{
  "modules": {
    "0.47383876715576023": {
      "definition": "//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "472.75951622559387",
      "left": "2639.4814367218314",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.07944144280928633": {
      "definition": "//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "817.6387035102925",
      "left": "3929.196555336137",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.8903773266711255": {
      "definition": "//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "989.3193569388857",
      "left": "4460.994888925579",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.3135579179893032": {
      "definition": "//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '15.748000000000005'\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "695.5727433438024",
      "left": "3411.8364045475028",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.749132408760488": {
      "definition": "//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "1155.0172844553833",
      "left": "4980.623236372596",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.4793941661670936": {
      "definition": "//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n",
      "top": "1501.2514939300813",
      "left": "4079.6965146873954",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.6248369051648597": {
      "definition": "//\n// view toolpath\n//\n// Quentin Bolsee\n// (c) Massachusetts Institute of Technology 2024\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// todo:\n//     erase and update new path\n//     show depth info\n//     show size\n//     calculate camera far\n//\n// Fran notes:\n// From r125 you can no longer use the geometry contructor to create lines,\n// now you must use the buffer geometry constructor. The process is slightly\n// different.\n//\n//\n// closure\n//\n(function(){\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'view multi_toolpath'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.colormap = [\n            0xbf00ff,\n            0xffbf00,\n            0x0080ff,\n            0xbfff00,\n            0x00ffff,\n            0xff00bf\n        ]\n    }\n    //\n    // inputs\n    //\n    var inputs = {\n        toolpaths: {\n            type:'object',\n            event: function(evt) {\n                var t = evt.detail;\n                mod.paths = t.paths\n                mod.tools = t.tools\n                mod.name = t.name\n                mod.dpi = t.dpi\n                mod.width = t.width\n                mod.height = t.height\n                mod.depth = t.depth\n\n                show_path_info()\n                show_path()\n                outputs.toolpaths.event()\n            }\n        }\n    }\n    //\n    // outputs\n    //\n    var outputs = {\n        toolpaths: {\n            type: 'object',\n            event: function() {\n                cmd = {}\n                cmd.paths = mod.paths\n                cmd.tools = mod.tools\n                cmd.name = mod.name\n                cmd.dpi = mod.dpi\n                cmd.width = mod.width\n                cmd.height = mod.height\n                mods.output(mod,'toolpaths',cmd)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div){\n        mod.div = div\n        //\n        // info\n        //\n        var text = document.createTextNode('number: ')\n            div.appendChild(text)\n            mod.numbertext = text\n        div.appendChild(document.createElement('br'))\n        var text = document.createTextNode('(mm)')\n            div.appendChild(text)\n            mod.mmtext = text\n        div.appendChild(document.createElement('br'))\n        var text = document.createTextNode('(in)')\n            div.appendChild(text)\n            mod.intext = text\n        //\n        // view\n        //\n        div.appendChild(document.createElement('br'))\n        var btn = document.createElement('button')\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            var span = document.createElement('span')\n                var text = document.createTextNode('view')\n                    span.appendChild(text)\n                btn.appendChild(span)\n            btn.addEventListener('click',function(){\n                open_view_window()\n                })\n            div.appendChild(btn)\n        }\n    //\n    // local functions\n    //\n    // show_path_info\n    //\n    function show_path_info() {\n        mod.numbertext.nodeValue = 'paths: '+mod.paths.length\n        var width = (25.4*mod.width/mod.dpi).toFixed(3)\n        var height = (25.4*mod.height/mod.dpi).toFixed(3)\n        var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n        if (mod.depth == undefined)\n            mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n        else\n            mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n        var width = (mod.width/mod.dpi).toFixed(3)\n        var height = (mod.height/mod.dpi).toFixed(3)\n        var depth = (mod.depth/mod.dpi).toFixed(3)\n        if (mod.depth == undefined)\n            mod.intext.nodeValue = width+' x '+height+' (in)'\n        else\n            mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n        mods.fit(mod.div)\n        }\n    //\n    // show_path\n    //\n    function show_path() {\n        var scene = mod.scene\n        var camera = mod.camera\n        var renderer = mod.renderer\n        //\n        // check if view window open\n        //\n        if (mod.win == undefined) {\n            open_view_window()\n            return\n            }\n        //\n        // check for path\n        //\n        if (mod.paths == undefined)\n            return\n        //\n        // clear scene, leave camera\n        //\n        var length = scene.children.length\n        for (var c = (length-1); c > 1; --c) {\n            scene.remove(scene.children[c])\n            }\n        //\n        // fit camera\n        //\n        mod.thetaxy = 0\n        mod.thetaz = 0\n        mod.r = mod.height/2\n        mod.x0 = mod.width/2\n        mod.y0 = mod.height/2\n        camera.position.set(mod.x0,mod.y0,mod.r)\n        camera.up = new THREE.Vector3(0,1,0)\n        camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n        camera.updateProjectionMatrix()\n        //\n        // draw segments\n        //\n        var arrow_size = 1+mod.width/200\n\n        for (var i = 0; i < mod.paths.length; i++) {\n            var path = mod.paths[i];\n            var k = (mod.tools[i] - 1) % mod.colormap.length; // iterate over colors\n\n            for (var segment = 0; segment < path.length; ++segment) {\n                if (segment > 0)\n                    add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)\n                for (var point = 1; point < path[segment].length; ++point) {\n                    add_arrow(path[segment][point-1],path[segment][point],mod.colormap[k],arrow_size)\n                }\n            }\n        }\n\n        //\n        // add axes\n        //\n        var length = mod.height/10\n        add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n        add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n        add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n        //\n        // render\n        //\n        update()\n        //\n        // add_arrow\n        //\n        function add_arrow(start,stop,color,size) {\n            var origin = new THREE.Vector3().fromArray(start)\n            if (mod.depth == undefined)\n                origin.z = 0\n            var end  = new THREE.Vector3().fromArray(stop)\n            if (mod.depth == undefined)\n                end.z = 0\n            var length = new THREE.Vector3().subVectors(end,origin).length()\n            if (length <= size) {\n                add_line(origin,end,color)\n                //length = 1.1*size\n                return\n                }\n            var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n            var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n            scene.add(arrow)\n            }\n        //\n        // add_line\n        //\n        function add_line(start,stop,colorhex) {\n            var vertices = []\n            vertices.push(start,stop)\n            var geometry = new THREE.BufferGeometry().setFromPoints(vertices)\n            var material = new THREE.LineBasicMaterial({color:colorhex})\n            var line = new THREE.Line(geometry,material)\n            scene.add(line)\n            }\n        //\n        // update\n        //\n        function update() {\n            renderer.render(scene,camera)\n            }\n        }\n    //\n    // open_view_window\n    //\n    function open_view_window() {\n        //\n        // globals\n        //\n        var container,scene,camera,renderer,win,controls\n        //\n        // open the window\n        //\n        open_window()\n        //\n        // open_window\n        //\n        function open_window() {\n            //\n            // open window\n            //\n            win = window.open('')\n            mod.win = win\n            //\n            // load three.js\n            //\n            var script = document.createElement('script')\n            script.type = 'text/javascript'\n            script.onload = init_window\n            script.src = 'js/three.js/three.min.js'\n            mod.div.appendChild(script)\n            }\n        //\n        // init_window\n        //\n        function init_window() {\n            //\n            // close button\n            //\n            var btn = document.createElement('button')\n                btn.appendChild(document.createTextNode('close'))\n                btn.style.padding = mods.ui.padding\n                btn.style.margin = 1\n                btn.addEventListener('click',function(){\n                    win.close()\n                    mod.win = undefined\n                    })\n                win.document.body.appendChild(btn)\n            //\n            // label text\n            //\n            var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n                win.document.body.appendChild(text)\n            //\n            // GL container\n            //\n            win.document.body.appendChild(document.createElement('br'))\n            container = win.document.createElement('div')\n            container.style.overflow = 'hidden'\n            win.document.body.appendChild(container)\n            //\n            // event handlers\n            //\n            container.addEventListener('contextmenu',context_menu)\n            container.addEventListener('mousedown',mouse_down)\n            container.addEventListener('mouseup',mouse_up)\n            container.addEventListener('mousemove',mouse_move)\n            container.addEventListener('wheel',mouse_wheel)\n            //\n            // add scene\n            //\n            scene = new THREE.Scene()\n            mod.scene = scene\n            var width = win.innerWidth\n            var height = win.innerHeight\n            var aspect = width/height\n            var near = 0.1\n            var far = 1000000\n            camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n            mod.camera = camera\n            scene.add(camera)\n            //\n            // add renderer\n            //\n            renderer = new THREE.WebGLRenderer({antialias:true})\n            mod.renderer = renderer\n            renderer.setClearColor(0xffffff)\n            renderer.setSize(width,height)\n            container.appendChild(renderer.domElement)\n            //\n            // show the path if available\n            //\n            show_path()\n            }\n        //\n        // context_menu\n        //\n        function context_menu(evt) {\n            evt.preventDefault()\n            evt.stopPropagation()\n            return (false)\n            }\n        //\n        // mouse_down\n        //\n        function mouse_down(evt) {\n            evt.preventDefault()\n            evt.stopPropagation()\n            mod.button = evt.button\n            mod.x = evt.clientX\n            mod.y = evt.clientY\n            }\n        //\n        // mouse_up\n        //\n        function mouse_up(evt) {\n            mod.button = undefined\n            mod.x = evt.clientX\n            mod.y = evt.clientY\n            }\n        //\n        // mouse_move\n        //\n        function mouse_move(evt) {\n            evt.preventDefault()\n            evt.stopPropagation()\n            var dx = evt.clientX-mod.x\n            var dy = evt.clientY-mod.y\n            mod.x = evt.clientX\n            mod.y = evt.clientY\n            if (mod.button == 0) {\n                mod.x0 +=\n                    Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n                    -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n                mod.y0 +=\n                    Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n                    +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n                camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n                camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n                camera.position.z = mod.r*Math.cos(mod.thetaxy)\n                camera.position.z = mod.r*Math.cos(mod.thetaxy)\n                camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n                camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n                camera.updateProjectionMatrix()\n                renderer.render(scene,camera)\n                }\n            else if (mod.button == 2) {\n                mod.thetaxy += dy/win.innerHeight\n                mod.thetaz += dx/win.innerWidth\n                camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n                camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n                camera.position.z = mod.r*Math.cos(mod.thetaxy)\n                camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n                camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n                camera.updateProjectionMatrix()\n                renderer.render(scene,camera)\n                }\n            }\n        //\n        // mouse_wheel\n        //\n        function mouse_wheel(evt) {\n            evt.preventDefault()\n            evt.stopPropagation()\n            var dy = evt.deltaY/win.innerHeight\n            mod.r += mod.height*dy\n            camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n            camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n            camera.position.z = mod.r*Math.cos(mod.thetaxy)\n            camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n            camera.updateProjectionMatrix()\n            renderer.render(scene,camera)\n            }\n        }\n    //\n    // return values\n    //\n    return ({\n        name:name,\n        init:init,\n        inputs:inputs,\n        outputs:outputs,\n        interface:interface\n        })\n    }())\n",
      "top": "1766.7929756127",
      "left": "2667.879766427708",
      "filename": "undefined",
      "inputs": {},
      "outputs": {}
    },
    "0.04917193113010909": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '350'\n        mod.text.value = 'Calculate ↓'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "629.3638371957819",
      "left": "1999.874182985904",
      "filename": "modules/ui/label%20simple.js",
      "inputs": {},
      "outputs": {}
    },
    "0.617082861732787": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '350'\n        mod.text.value = 'Traces image ↓'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "224.7779388284406",
      "left": "219.08990815494803",
      "filename": "modules/ui/label%20simple.js",
      "inputs": {},
      "outputs": {}
    },
    "0.7763537088288486": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '350'\n        mod.text.value = 'Exterior image ↓'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "963.7766797860675",
      "left": "224.50791092266354",
      "filename": "modules/ui/label%20simple.js",
      "inputs": {},
      "outputs": {}
    },
    "0.8043554368430617": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '350'\n        mod.text.value = 'Tabs ↓'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "1296.4027528240094",
      "left": "1027.9960388075801",
      "filename": "modules/character/convert.js",
      "inputs": {},
      "outputs": {}
    },
    "0.010036339038305142": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '350'\n        mod.text.value = 'View ↓'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "1607.9096887887845",
      "left": "2719.300841584381",
      "filename": "modules/ui/label%20simple.js",
      "inputs": {},
      "outputs": {}
    },
    "0.608045696421487": {
      "definition": "//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n// Modified by Francisco Sanchez Arroyo 31-Jan-2020\n// Modified by Sol Bekic 21-Jul-2020\n// Modified by Quentin Bolsee 2024-10-05\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'mill raster 2D'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.dia_in.value = 0.0156\n        mod.dia_mm.value = 25.4 * parseFloat(mod.dia_in.value)\n        mod.cut_in.value = 0.004\n        mod.cut_mm.value = 25.4 * parseFloat(mod.cut_in.value)\n        mod.max_in.value = 0.004\n        mod.max_mm.value = 25.4 * parseFloat(mod.max_in.value)\n        mod.number.value = 4\n        mod.stepover.value = 0.5\n        mod.merge.value = 1\n        mod.sort.checked = true\n    }\n    //\n    // inputs\n    //\n    var inputs = {\n        imageInfo: {\n            type: 'object',\n            event: function(evt) {\n                mod.name = evt.detail.name\n                mod.dpi = evt.detail.dpi\n                mod.width = evt.detail.width\n                mod.height = evt.detail.height\n                var ctx = mod.img.getContext(\"2d\")\n                ctx.canvas.width = mod.width\n                ctx.canvas.height = mod.height\n            }\n        },\n        path: {\n            type: 'array',\n            event: function(evt) {\n                if (mod.label.nodeValue == 'calculating') {\n                    draw_path(evt.detail)\n                    accumulate_path(evt.detail)\n                    mod.offsetCount += 1\n                    if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n                        mod.offset += parseFloat(mod.stepover.value)\n                        outputs.offset.event()\n                    } else {\n                        mod.label.nodeValue = 'calculate'\n                        mod.labelspan.style.fontWeight = 'normal'\n                        merge_path()\n                        clear_path()\n                        draw_path(mod.path)\n                        draw_connections()\n                        add_depth()\n                        outputs.toolpath.event()\n                    }\n                }\n            }\n        },\n        settings: {\n            type: 'object',\n            event: function(evt) {\n                set_values(evt.detail)\n            }\n        }\n    }\n    //\n    // outputs\n    //\n    var outputs = {\n        diameter: {\n            type: 'number',\n            event: function() {\n                mods.output(mod, 'diameter', Math.ceil(mod.dpi * mod.dia_in.value))\n            }\n        },\n        offset: {\n            type: 'number',\n            event: function() {\n                var pixels = mod.offset * parseFloat(mod.dia_in.value) * mod.dpi\n                mods.output(mod, 'offset', pixels)\n            }\n        },\n        toolpath: {\n            type: 'object',\n            event: function() {\n                cmd = {}\n                cmd.path = mod.path\n                cmd.name = mod.name\n                cmd.dpi = mod.dpi\n                cmd.width = mod.width\n                cmd.height = mod.height\n                cmd.depth = mod.depth\n                mods.output(mod, 'toolpath', cmd)\n            }\n        }\n    }\n    var click_calculate = function() {\n        mod.label.nodeValue = 'calculating'\n        mod.labelspan.style.fontWeight = 'bold'\n        mod.offset = 0.5\n        mod.offsetCount = 0\n        mod.path = []\n        clear_path()\n        outputs.diameter.event()\n        outputs.offset.event()\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //\n        // tool diameter\n        //\n        div.appendChild(document.createTextNode('tool diameter'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('mm: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.dia_in.value = parseFloat(mod.dia_mm.value) / 25.4\n        })\n        div.appendChild(input)\n        mod.dia_mm = input\n        div.appendChild(document.createTextNode(' in: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.dia_mm.value = parseFloat(mod.dia_in.value) * 25.4\n        })\n        div.appendChild(input)\n        mod.dia_in = input\n        div.appendChild(document.createElement('br'))\n        //\n        // cut depth\n        //\n        div.appendChild(document.createTextNode('cut depth'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('mm: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.cut_in.value = parseFloat(mod.cut_mm.value) / 25.4\n        })\n        div.appendChild(input)\n        mod.cut_mm = input\n        div.appendChild(document.createTextNode(' in: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.cut_mm.value = parseFloat(mod.cut_in.value) * 25.4\n        })\n        div.appendChild(input)\n        mod.cut_in = input\n        div.appendChild(document.createElement('br'))\n        //\n        // max depth\n        //\n        div.appendChild(document.createTextNode('max depth'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('mm: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.max_in.value = parseFloat(mod.max_mm.value) / 25.4\n        })\n        div.appendChild(input)\n        mod.max_mm = input\n        div.appendChild(document.createTextNode(' in: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.max_mm.value = parseFloat(mod.max_in.value) * 25.4\n        })\n        div.appendChild(input)\n        mod.max_in = input\n        div.appendChild(document.createElement('br'))\n        //\n        // offset number\n        //\n        div.appendChild(document.createTextNode('offset number: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        div.appendChild(input)\n        mod.number = input\n        div.appendChild(document.createTextNode(' (0 = fill)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // offset stepover\n        //\n        div.appendChild(document.createTextNode('offset stepover: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        div.appendChild(input)\n        mod.stepover = input\n        div.appendChild(document.createTextNode(' (1 = diameter)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // direction\n        //\n        div.appendChild(document.createTextNode('direction: '))\n        div.appendChild(document.createTextNode('climb'))\n        var input = document.createElement('input')\n        input.type = 'radio'\n        input.name = mod.div.id + 'direction'\n        input.id = mod.div.id + 'climb'\n        input.checked = true\n        div.appendChild(input)\n        mod.climb = input\n        div.appendChild(document.createTextNode(' conventional'))\n        var input = document.createElement('input')\n        input.type = 'radio'\n        input.name = mod.div.id + 'direction'\n        input.id = mod.div.id + 'conventional'\n        div.appendChild(input)\n        mod.conventional = input\n        div.appendChild(document.createElement('br'))\n        //\n        // path merge\n        //\n        div.appendChild(document.createTextNode('path merge: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        div.appendChild(input)\n        mod.merge = input\n        div.appendChild(document.createTextNode(' (1 = diameter)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // path order\n        //\n        div.appendChild(document.createTextNode('path order: '))\n        div.appendChild(document.createTextNode('forward'))\n        var input = document.createElement('input')\n        input.type = 'radio'\n        input.name = mod.div.id + 'order'\n        input.id = mod.div.id + 'forward'\n        input.checked = true\n        div.appendChild(input)\n        mod.forward = input\n        div.appendChild(document.createTextNode(' reverse'))\n        var input = document.createElement('input')\n        input.type = 'radio'\n        input.name = mod.div.id + 'order'\n        input.id = mod.div.id + 'reverse'\n        div.appendChild(input)\n        mod.reverse = input\n        div.appendChild(document.createElement('br'))\n        //\n        // sort distance\n        //\n        div.appendChild(document.createTextNode('sort distance: '))\n        var input = document.createElement('input')\n        input.type = 'checkbox'\n        input.id = mod.div.id + 'sort'\n        div.appendChild(input)\n        mod.sort = input\n        div.appendChild(document.createElement('br'))\n        //\n        // calculate\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        var span = document.createElement('span')\n        var text = document.createTextNode('calculate')\n        mod.label = text\n        span.appendChild(text)\n        mod.labelspan = span\n        btn.appendChild(span)\n        btn.addEventListener('click', click_calculate)\n        btn.disabled = true\n        div.appendChild(btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        // view\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('view'))\n        btn.addEventListener('click', function() {\n            var win = window.open('')\n            var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click', function() {\n                win.close()\n            })\n            win.document.body.appendChild(btn)\n            win.document.body.appendChild(document.createElement('br'))\n            var svg = document.getElementById(mod.div.id + 'svg')\n            var clone = svg.cloneNode(true)\n            clone.setAttribute('width', mod.img.width)\n            clone.setAttribute('height', mod.img.height)\n            win.document.body.appendChild(clone)\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // on-screen SVG\n        //\n        var svgNS = \"http://www.w3.org/2000/svg\"\n        var svg = document.createElementNS(svgNS, \"svg\")\n        svg.setAttribute('id', mod.div.id + 'svg')\n        svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n            \"xmlns:xlink\", \"http://www.w3.org/1999/xlink\")\n        svg.setAttribute('width', mods.ui.canvas)\n        svg.setAttribute('height', mods.ui.canvas)\n        svg.style.backgroundColor = 'rgb(255,255,255)'\n        var g = document.createElementNS(svgNS, 'g')\n        g.setAttribute('id', mod.div.id + 'g')\n        svg.appendChild(g)\n        div.appendChild(svg)\n        div.appendChild(document.createElement('br'))\n        //\n        // off-screen image canvas\n        //\n        var canvas = document.createElement('canvas')\n        mod.img = canvas\n    }\n    //\n    // local functions\n    //\n    // set_values\n    //\n    function set_values(settings) {\n        for (var s in settings) {\n            switch (s) {\n                case 'tool diameter (in)':\n                    mod.dia_in.value = settings[s]\n                    mod.dia_mm.value = parseFloat(mod.dia_in.value) * 25.4\n                    break\n                case 'cut depth (in)':\n                    mod.cut_in.value = settings[s]\n                    mod.cut_mm.value = parseFloat(mod.cut_in.value) * 25.4\n                    break\n                case 'max depth (in)':\n                    mod.max_in.value = settings[s]\n                    mod.max_mm.value = parseFloat(mod.max_in.value) * 25.4\n                    break\n                case 'tool diameter (mm)':\n                    mod.dia_mm.value = settings[s]\n                    mod.dia_in.value = parseFloat(mod.dia_mm.value) / 25.4\n                    break\n                case 'cut depth (mm)':\n                    mod.cut_mm.value = settings[s]\n                    mod.cut_in.value = parseFloat(mod.cut_mm.value) / 25.4\n                    break\n                case 'max depth (mm)':\n                    mod.max_mm.value = settings[s]\n                    mod.max_in.value = parseFloat(mod.max_mm.value) / 25.4\n                    break\n                case 'offset number':\n                    mod.number.value = settings[s]\n                    break\n                case 'overlap':\n                case 'offset stepover':\n                    mod.stepover.value = settings[s]\n                    break\n                case 'calculate':\n                    if (settings[s]) {\n                       click_calculate()\n                    }\n                    break\n            }\n        }\n    }\n    //\n    // clear_path\n    //\n    function clear_path() {\n        var svg = document.getElementById(mod.div.id + 'svg')\n        svg.setAttribute('viewBox', \"0 0 \" + (mod.img.width - 1) + \" \" + (mod.img.height - 1))\n        var g = document.getElementById(mod.div.id + 'g')\n        svg.removeChild(g)\n        var g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n        g.setAttribute('id', mod.div.id + 'g')\n        svg.appendChild(g)\n    }\n    //\n    // accumulate_path\n    //    todo: replace inefficient insertion sort\n    //    todo: move sort out of main thread\n    //\n    function accumulate_path(path) {\n        var forward = mod.forward.checked\n        var conventional = mod.conventional.checked\n        var sort = mod.sort.checked\n        for (var segnew = 0; segnew < path.length; ++segnew) {\n            if (conventional)\n                path[segnew].reverse()\n            if (mod.path.length == 0)\n                mod.path.splice(0, 0, path[segnew])\n            else if (sort) {\n                var xnew = path[segnew][0][0]\n                var ynew = path[segnew][0][1]\n                var dmin = Number.MAX_VALUE\n                var segmin = -1\n                for (var segold = 0; segold < mod.path.length; ++segold) {\n                    var xold = mod.path[segold][0][0]\n                    var yold = mod.path[segold][0][1]\n                    var dx = xnew - xold\n                    var dy = ynew - yold\n                    var d = Math.sqrt(dx * dx + dy * dy)\n                    if (d < dmin) {\n                        dmin = d\n                        segmin = segold\n                    }\n                }\n                if (forward)\n                    mod.path.splice(segmin + 1, 0, path[segnew])\n                else\n                    mod.path.splice(segmin, 0, path[segnew])\n            } else {\n                if (forward)\n                    mod.path.splice(mod.path.length, 0, path[segnew])\n                else\n                    mod.path.splice(0, 0, path[segnew])\n            }\n        }\n    }\n    //\n    // merge_path\n    //\n    function merge_path() {\n        var dmerge = mod.dpi * parseFloat(mod.merge.value) * parseFloat(mod.dia_in.value)\n        var seg = 0\n        while (seg < (mod.path.length - 1)) {\n            var xold = mod.path[seg][mod.path[seg].length - 1][0]\n            var yold = mod.path[seg][mod.path[seg].length - 1][1]\n            var xnew = mod.path[seg + 1][0][0]\n            var ynew = mod.path[seg + 1][0][1]\n            var dx = xnew - xold\n            var dy = ynew - yold\n            var d = Math.sqrt(dx * dx + dy * dy)\n            if (d < dmerge)\n                mod.path.splice(seg, 2, mod.path[seg].concat(mod.path[seg + 1]))\n            else\n                seg += 1\n        }\n    }\n    //\n    // add_depth\n    //\n    function add_depth() {\n        var cut = parseFloat(mod.cut_in.value)\n        var max = parseFloat(mod.max_in.value)\n        var newpath = []\n        for (var seg = 0; seg < mod.path.length; ++seg) {\n            var depth = cut\n            if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length - 1][0]) &&\n                (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length - 1][0])) {\n                var newseg = []\n                while (depth <= max) {\n                    var idepth = -Math.round(mod.dpi * depth)\n                    for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n                        var point = mod.path[seg][pt].concat(idepth)\n                        newseg.splice(newseg.length, 0, point)\n                    }\n                    if (depth == max)\n                        break\n                    depth += cut\n                    if (depth > max)\n                        depth = max\n                }\n                newpath.splice(newpath.length, 0, newseg)\n            } else {\n                var newseg = []\n                while (depth <= max) {\n                    var idepth = -Math.round(mod.dpi * depth)\n                    for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n                        var point = mod.path[seg][pt].concat(idepth)\n                        newseg.splice(newseg.length, 0, point)\n                    }\n                    newpath.splice(newpath.length, 0, newseg)\n                    newseg = []\n                    if (depth == max)\n                        break\n                    depth += cut\n                    if (depth > max)\n                        depth = max\n                }\n            }\n        }\n        mod.path = newpath\n        mod.depth = Math.round(parseFloat(mod.max_in.value) * mod.dpi)\n    }\n    //\n    // draw_path\n    //\n    function draw_path(path) {\n        var g = document.getElementById(mod.div.id + 'g')\n        var h = mod.img.height\n        var w = mod.img.width\n        var xend = null\n        var yend = null\n        //\n        // loop over segments\n        //\n        for (var segment = 0; segment < path.length; ++segment) {\n            if (path[segment].length > 1) {\n                //\n                // loop over points\n                //\n                for (var point = 1; point < path[segment].length; ++point) {\n                    var line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\n                    line.setAttribute('stroke', 'black')\n                    line.setAttribute('stroke-width', 1)\n                    line.setAttribute('stroke-linecap', 'round')\n                    var x1 = path[segment][point - 1][0]\n                    var y1 = h - path[segment][point - 1][1] - 1\n                    var x2 = path[segment][point][0]\n                    var y2 = h - path[segment][point][1] - 1\n                    xend = x2\n                    yend = y2\n                    line.setAttribute('x1', x1)\n                    line.setAttribute('y1', y1)\n                    line.setAttribute('x2', x2)\n                    line.setAttribute('y2', y2)\n                    var dx = x2 - x1\n                    var dy = y2 - y1\n                    var d = Math.sqrt(dx * dx + dy * dy)\n                    if (d > 0) {\n                        nx = 6 * dx / d\n                        ny = 6 * dy / d\n                        var tx = 3 * dy / d\n                        var ty = -3 * dx / d\n                        g.appendChild(line)\n                        triangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n                        triangle.setAttribute('points', x2 + ',' + y2 + ' ' + (x2 - nx + tx) + ',' + (y2 - ny + ty) +\n                            ' ' + (x2 - nx - tx) + ',' + (y2 - ny - ty))\n                        triangle.setAttribute('fill', 'black')\n                        g.appendChild(triangle)\n                    }\n                }\n            }\n        }\n    }\n    //\n    // draw_connections\n    //\n    function draw_connections() {\n        var g = document.getElementById(mod.div.id + 'g')\n        var h = mod.img.height\n        var w = mod.img.width\n        //\n        // loop over segments\n        //\n        for (var segment = 1; segment < mod.path.length; ++segment) {\n            //\n            // draw connection from previous segment\n            //\n            var line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\n            line.setAttribute('stroke', 'red')\n            line.setAttribute('stroke-width', 1)\n            line.setAttribute('stroke-linecap', 'round')\n            var x1 = mod.path[segment - 1][mod.path[segment - 1].length - 1][0]\n            var y1 = h - mod.path[segment - 1][mod.path[segment - 1].length - 1][1] - 1\n            var x2 = mod.path[segment][0][0]\n            var y2 = h - mod.path[segment][0][1] - 1\n            line.setAttribute('x1', x1)\n            line.setAttribute('y1', y1)\n            line.setAttribute('x2', x2)\n            line.setAttribute('y2', y2)\n            var dx = x2 - x1\n            var dy = y2 - y1\n            var d = Math.sqrt(dx * dx + dy * dy)\n            if (d > 0) {\n                nx = 6 * dx / d\n                ny = 6 * dy / d\n                var tx = 3 * dy / d\n                var ty = -3 * dx / d\n                g.appendChild(line)\n                triangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n                triangle.setAttribute('points', x2 + ',' + y2 + ' ' + (x2 - nx + tx) + ',' + (y2 - ny + ty) +\n                    ' ' + (x2 - nx - tx) + ',' + (y2 - ny - ty))\n                triangle.setAttribute('fill', 'red')\n                g.appendChild(triangle)\n            }\n        }\n    }\n    //\n    // return values\n    //\n    return ({\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "931.509607690037",
      "left": "2705.2811447638696",
      "filename": "modules/15%20processes/20%20mill%20raster/2D.js",
      "inputs": {},
      "outputs": {}
    },
    "0.6845090847457781": {
      "definition": "//\n// save file\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'append to file'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.name = ''\n        mod.contents = ''\n        mod.text.value = 'M6 T-1 % drop tool\\nM496.1'\n\n    }\n    //\n    // inputs\n    //\n    var inputs = {\n        file: {\n            type: 'object',\n            event: function(evt) {\n                mod.name = evt.detail.name\n                mod.contents = evt.detail.contents\n                update_output()\n            }\n        }\n    }\n    //\n    // outputs\n    //\n    var outputs = {\n        file: {\n            type:'',\n            event: function(obj) {\n                mods.output(mod,'file',obj)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //\n        // info\n        //\n        var text = document.createTextNode('name:')\n        div.appendChild(text)\n        mod.nametext = text\n        div.appendChild(document.createElement('br'))\n        var text = document.createTextNode('size:')\n        div.appendChild(text)\n        mod.sizetext = text\n        div.appendChild(document.createElement('br'))\n        //\n        // text area\n        //\n        var text = document.createElement('textarea')\n        text.setAttribute('rows', mods.ui.rows)\n        text.setAttribute('cols', mods.ui.cols)\n        //\n        // watch textarea for resize\n        //\n        new MutationObserver(update_module).observe(text, {\n            attributes: true,\n            attributeFilter: [\"style\"]\n        })\n        div.appendChild(text)\n        mod.text = text\n        div.appendChild(document.createElement('br'))\n    }\n    //\n    // local functions\n    //\n    function update_output(event) {\n        //\n        // send file\n        //\n        var obj = {}\n        obj.type = ''\n        obj.name = mod.name\n        obj.contents = mod.contents.concat(mod.text.value)\n        outputs.file.event(obj) // output as fiie\n        mod.nametext.nodeValue = 'name: '+mod.name\n        mods.fit(mod.div)\n        mod.sizetext.nodeValue = 'size: '+mod.contents.length\n        mods.fit(mod.div)\n    }\n    function update_module() {\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "1599.5319624893739",
      "left": "3694.651645823933",
      "filename": "modules/file/append.js",
      "inputs": {},
      "outputs": {}
    },
    "0.8534257529052124": {
      "definition": "//\r\n// image invert\r\n//\r\n// Quentin Bolsee\r\n// (c) Massachusetts Institute of Technology 2024\r\n//\r\n// This work may be reproduced, modified, distributed, performed, and\r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is\r\n// provided as is; no warranty is provided, and users accept all\r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function() {\r\n    //\r\n    // module globals\r\n    //\r\n    var mod = {}\r\n    //\r\n    // name\r\n    //\r\n    var name = 'image invert'\r\n    //\r\n    // initialization\r\n    //\r\n    var init = function() { }\r\n    //\r\n    // inputs\r\n    //\r\n    var inputs = {\r\n        image: {\r\n            type: 'RGBA',\r\n            event: function(evt) {\r\n                mod.input = evt.detail\r\n                var ctx = mod.img.getContext(\"2d\")\r\n                ctx.canvas.width = mod.input.width\r\n                ctx.canvas.height = mod.input.height\r\n                ctx.putImageData(mod.input, 0, 0)\r\n                image_invert()\r\n            }\r\n        }\r\n    }\r\n    //\r\n    // outputs\r\n    //\r\n    var outputs = {\r\n        image: {\r\n            type: 'RGBA',\r\n            event: function() {\r\n                var ctx = mod.img.getContext(\"2d\")\r\n                var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\r\n                mods.output(mod,'image',img)\r\n            }\r\n        }\r\n    }\r\n    //\r\n    // interface\r\n    //\r\n    var interface = function(div) {\r\n        mod.div = div\r\n        //\r\n        // on-screen drawing canvas\r\n        //\r\n        var canvas = document.createElement('canvas')\r\n        canvas.width = mods.ui.canvas\r\n        canvas.height = mods.ui.canvas\r\n        canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n        div.appendChild(canvas)\r\n        mod.canvas = canvas\r\n        div.appendChild(document.createElement('br'))\r\n        //\r\n        // off-screen image canvas\r\n        //\r\n        var canvas = document.createElement('canvas')\r\n        mod.img = canvas\r\n        //\r\n        // view button\r\n        //\r\n        var btn = document.createElement('button')\r\n        btn.style.padding = mods.ui.padding\r\n        btn.style.margin = 1\r\n        btn.appendChild(document.createTextNode('view'))\r\n        btn.addEventListener('click', function() {\r\n            var win = window.open('')\r\n            var btn = document.createElement('button')\r\n            btn.appendChild(document.createTextNode('close'))\r\n            btn.style.padding = mods.ui.padding\r\n            btn.style.margin = 1\r\n            btn.addEventListener('click', function() {\r\n                win.close()\r\n            })\r\n            win.document.body.appendChild(btn)\r\n            win.document.body.appendChild(document.createElement('br'))\r\n            var canvas = document.createElement('canvas')\r\n            canvas.width = mod.img.width\r\n            canvas.height = mod.img.height\r\n            win.document.body.appendChild(canvas)\r\n            var ctx = canvas.getContext(\"2d\")\r\n            ctx.drawImage(mod.img, 0, 0)\r\n        })\r\n        div.appendChild(btn)\r\n    }\r\n    //\r\n    // local functions\r\n    //\r\n    function image_invert() {\r\n        var blob = new Blob(['(' + worker.toString() + '())'])\r\n        var url = window.URL.createObjectURL(blob)\r\n        var webworker = new Worker(url)\r\n        webworker.addEventListener('message', function(evt) {\r\n            window.URL.revokeObjectURL(url)\r\n            var h = mod.img.height\r\n            var w = mod.img.width\r\n            var buf = new Uint8ClampedArray(evt.data.buffer)\r\n            var imgdata = new ImageData(buf,w,h)\r\n            var ctx = mod.img.getContext(\"2d\")\r\n            ctx.putImageData(imgdata,0,0)\r\n            if (w > h) {\r\n                var x0 = 0\r\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\r\n                var wd = mod.canvas.width\r\n                var hd = mod.canvas.width * h / w\r\n            } else {\r\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\r\n                var y0 = 0\r\n                var wd = mod.canvas.height * w / h\r\n                var hd = mod.canvas.height\r\n            }\r\n            var ctx = mod.canvas.getContext(\"2d\")\r\n            ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\r\n            ctx.drawImage(mod.img,x0,y0,wd,hd)\r\n            webworker.terminate()\r\n            outputs.image.event()\r\n            })\r\n        var ctx = mod.canvas.getContext(\"2d\")\r\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\r\n        var ctx = mod.img.getContext(\"2d\")\r\n        ctx.putImageData(mod.input,0,0)\r\n        var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\r\n        webworker.postMessage({\r\n            height: mod.input.height,\r\n            width: mod.input.width,\r\n            buffer: img.data.buffer\r\n            }, [img.data.buffer])\r\n\r\n    }\r\n\r\n    function worker() {\r\n        self.addEventListener('message', function(evt) {\r\n            var h = evt.data.height\r\n            var w = evt.data.width\r\n            var buf = new Uint8ClampedArray(evt.data.buffer)\r\n\r\n            for (var row = 0; row < h; ++row) {\r\n                for (var col = 0; col < w; ++col) {\r\n                    buf[(h-1-row)*w*4+col*4+0] = 255 - buf[(h-1-row)*w*4+col*4+0]\r\n                    buf[(h-1-row)*w*4+col*4+1] = 255 - buf[(h-1-row)*w*4+col*4+1]\r\n                    buf[(h-1-row)*w*4+col*4+2] = 255 - buf[(h-1-row)*w*4+col*4+2]\r\n                    buf[(h-1-row)*w*4+col*4+3] = buf[(h-1-row)*w*4+col*4+3]\r\n                }\r\n            }\r\n            self.postMessage({\r\n                buffer: buf.buffer\r\n            }, [buf.buffer])\r\n        })\r\n    }\r\n    //\r\n    // return values\r\n    //\r\n    return ({\r\n        mod: mod,\r\n        name: name,\r\n        init: init,\r\n        inputs: inputs,\r\n        outputs: outputs,\r\n        interface: interface\r\n    })\r\n}())\r\n",
      "top": "948.2056501633256",
      "left": "761.978872586627",
      "filename": "modules/image/invert.js",
      "inputs": {},
      "outputs": {}
    },
    "0.543885257115264": {
      "definition": "//\r\n// image sum\r\n//\r\n// Quentin Bolsee\r\n// (c) Massachusetts Institute of Technology 2024\r\n//\r\n// This work may be reproduced, modified, distributed, performed, and\r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is\r\n// provided as is; no warranty is provided, and users accept all\r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function() {\r\n    //\r\n    // module globals\r\n    //\r\n    var mod = {}\r\n    //\r\n    // name\r\n    //\r\n    var name = 'image sum'\r\n    //\r\n    // initialization\r\n    //\r\n    var init = function() { }\r\n    //\r\n    // inputs\r\n    //\r\n    var inputs = {\r\n        image1: {\r\n            type: 'RGBA',\r\n            event: function(evt) {\r\n                mod.input1 = evt.detail\r\n                var ctx = mod.img1.getContext(\"2d\")\r\n                ctx.canvas.width = mod.input1.width\r\n                ctx.canvas.height = mod.input1.height\r\n                ctx.putImageData(mod.input1, 0, 0)\r\n                if (mod.input2 !== undefined) {\r\n                    image_sum()\r\n                }\r\n            }\r\n        },\r\n        image2: {\r\n            type: 'RGBA',\r\n            event: function(evt) {\r\n                mod.input2 = evt.detail\r\n                var ctx = mod.img2.getContext(\"2d\")\r\n                ctx.canvas.width = mod.input2.width\r\n                ctx.canvas.height = mod.input2.height\r\n                ctx.putImageData(mod.input2, 0, 0)\r\n                if (mod.input1 !== undefined) {\r\n                    image_sum()\r\n                }\r\n            }\r\n        }\r\n    }\r\n    //\r\n    // outputs\r\n    //\r\n    var outputs = {\r\n        image: {\r\n            type: 'RGBA',\r\n            event: function() {\r\n                var ctx = mod.img2.getContext(\"2d\")\r\n                var img = ctx.getImageData(0,0,mod.img2.width,mod.img2.height)\r\n                mods.output(mod,'image',img)\r\n            }\r\n        }\r\n    }\r\n    //\r\n    // interface\r\n    //\r\n    var interface = function(div) {\r\n        mod.div = div\r\n        //\r\n        // on-screen drawing canvas\r\n        //\r\n        var canvas = document.createElement('canvas')\r\n        canvas.width = mods.ui.canvas\r\n        canvas.height = mods.ui.canvas\r\n        canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n        div.appendChild(canvas)\r\n        mod.canvas = canvas\r\n        div.appendChild(document.createElement('br'))\r\n        //\r\n        // off-screen image canvas\r\n        //\r\n        var canvas = document.createElement('canvas')\r\n        mod.img1 = canvas\r\n        var canvas = document.createElement('canvas')\r\n        mod.img2 = canvas\r\n        //\r\n        // view button\r\n        //\r\n        var btn = document.createElement('button')\r\n        btn.style.padding = mods.ui.padding\r\n        btn.style.margin = 1\r\n        btn.appendChild(document.createTextNode('view'))\r\n        btn.addEventListener('click', function() {\r\n            var win = window.open('')\r\n            var btn = document.createElement('button')\r\n            btn.appendChild(document.createTextNode('close'))\r\n            btn.style.padding = mods.ui.padding\r\n            btn.style.margin = 1\r\n            btn.addEventListener('click', function() {\r\n                win.close()\r\n            })\r\n            win.document.body.appendChild(btn)\r\n            win.document.body.appendChild(document.createElement('br'))\r\n            var canvas = document.createElement('canvas')\r\n            canvas.width = mod.img2.width\r\n            canvas.height = mod.img2.height\r\n            win.document.body.appendChild(canvas)\r\n            var ctx = canvas.getContext(\"2d\")\r\n            ctx.drawImage(mod.img2, 0, 0)\r\n        })\r\n        div.appendChild(btn)\r\n    }\r\n    //\r\n    // local functions\r\n    //\r\n    function image_sum() {\r\n        var blob = new Blob(['(' + worker.toString() + '())'])\r\n        var url = window.URL.createObjectURL(blob)\r\n        var webworker = new Worker(url)\r\n        webworker.addEventListener('message', function(evt) {\r\n            window.URL.revokeObjectURL(url)\r\n            var h = mod.img2.height\r\n            var w = mod.img2.width\r\n            var buf = new Uint8ClampedArray(evt.data.buffer2)\r\n            var imgdata = new ImageData(buf,w,h)\r\n            var ctx = mod.img2.getContext(\"2d\")\r\n            ctx.putImageData(imgdata,0,0)\r\n            if (w > h) {\r\n                var x0 = 0\r\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\r\n                var wd = mod.canvas.width\r\n                var hd = mod.canvas.width * h / w\r\n            } else {\r\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\r\n                var y0 = 0\r\n                var wd = mod.canvas.height * w / h\r\n                var hd = mod.canvas.height\r\n            }\r\n            var ctx = mod.canvas.getContext(\"2d\")\r\n            ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\r\n            ctx.drawImage(mod.img2,x0,y0,wd,hd)\r\n            webworker.terminate()\r\n            outputs.image.event()\r\n            })\r\n        var ctx = mod.canvas.getContext(\"2d\")\r\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\r\n        var ctx = mod.img1.getContext(\"2d\")\r\n        ctx.putImageData(mod.input1,0,0)\r\n        var img1 = ctx.getImageData(0,0,mod.img1.width,mod.img1.height)\r\n        var ctx = mod.img2.getContext(\"2d\")\r\n        ctx.putImageData(mod.input2,0,0)\r\n        var img2 = ctx.getImageData(0,0,mod.img2.width,mod.img2.height)\r\n        webworker.postMessage({\r\n            height: mod.input1.height,\r\n            width: mod.input1.width,\r\n            buffer1: img1.data.buffer,\r\n            buffer2: img2.data.buffer\r\n        }, [img2.data.buffer])\r\n    }\r\n\r\n    function worker() {\r\n        self.addEventListener('message', function(evt) {\r\n            var h = evt.data.height\r\n            var w = evt.data.width\r\n            var buf1 = new Uint8ClampedArray(evt.data.buffer1)\r\n            var buf2 = new Uint8ClampedArray(evt.data.buffer2)\r\n\r\n            for (var row = 0; row < h; ++row) {\r\n                for (var col = 0; col < w; ++col) {\r\n                    buf2[(h-1-row)*w*4+col*4+0] = Math.min(255, buf1[(h-1-row)*w*4+col*4+0]+buf2[(h-1-row)*w*4+col*4+0])\r\n                    buf2[(h-1-row)*w*4+col*4+1] = Math.min(255, buf1[(h-1-row)*w*4+col*4+1]+buf2[(h-1-row)*w*4+col*4+1])\r\n                    buf2[(h-1-row)*w*4+col*4+2] = Math.min(255, buf1[(h-1-row)*w*4+col*4+2]+buf2[(h-1-row)*w*4+col*4+2])\r\n                    buf2[(h-1-row)*w*4+col*4+3] = Math.max(buf1[(h-1-row)*w*4+col*4+3], buf2[(h-1-row)*w*4+col*4+3])\r\n                }\r\n            }\r\n            self.postMessage({\r\n                buffer2: buf2.buffer\r\n            }, [buf2.buffer])\r\n        })\r\n    }\r\n    //\r\n    // return values\r\n    //\r\n    return ({\r\n        mod: mod,\r\n        name: name,\r\n        init: init,\r\n        inputs: inputs,\r\n        outputs: outputs,\r\n        interface: interface\r\n    })\r\n}())\r\n",
      "top": "825.3343419740456",
      "left": "1282.4079272251024",
      "filename": "modules/image/sum.js",
      "inputs": {},
      "outputs": {}
    },
    "0.1970821212694227": {
      "definition": "//\n// label\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n// Modified by Francisco Sanchez Arroyo 02-Feb-2020\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'label'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.size.value = '400'\n        mod.text.value = 'Carvera mill PCB, copper clear'\n        update_text()\n    }\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {}\n    //\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //div.appendChild(document.createTextNode('font size: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 3\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.size = input\n        //div.appendChild(document.createTextNode(' (%)'))\n        //div.appendChild(document.createElement('br'))\n        //div.appendChild(document.createTextNode('text: '))\n        input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function(evt) {\n            update_text()\n        })\n        //div.appendChild(input)\n        mod.text = input\n        //div.appendChild(document.createElement('br'))\n        var span = document.createElement('span')\n        var text = document.createTextNode('')\n        span.appendChild(text)\n        mod.label = text\n        div.appendChild(span)\n        mod.span = span\n    }\n    //\n    // local functions\n    //\n    function update_text() {\n        mod.label.nodeValue = mod.text.value\n        mod.span.style.fontSize = mod.size.value + '%'\n        mods.fit(mod.div)\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "42.004183621804486",
      "left": "2701.8947668317583",
      "filename": "modules/40%20ui/label%20simple.js",
      "inputs": {},
      "outputs": {}
    },
    "0.6627695112215364": {
      "definition": "//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// Modified by Fran Sanchez June 2021 (add flip v) and ChatGPT Feb 23 (add rotate cw)\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'read png'\n    //\n    // initialization\n    //\n    var init = function() {}\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {\n        image: {\n            type: 'RGBA',\n            event: function() {\n                var ctx = mod.img.getContext(\"2d\")\n                var img = ctx.getImageData(0, 0, mod.img.width, mod.img.height)\n                mods.output(mod, 'image', img)\n            }\n        },\n        imageInfo: {\n            type: 'object',\n            event: function() {\n                var obj = {}\n                obj.name = mod.name.nodeValue\n                obj.dpi = parseFloat(mod.dpitext.value)\n                obj.width = mod.img.width\n                obj.height = mod.img.height\n                mods.output(mod, 'imageInfo', obj)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //\n        // file input control\n        //\n        var file = document.createElement('input')\n        file.setAttribute('type', 'file')\n        file.setAttribute('id', div.id + 'file_input')\n        file.style.position = 'absolute'\n        file.style.left = 0\n        file.style.top = 0\n        file.style.width = 0\n        file.style.height = 0\n        file.style.opacity = 0\n        file.addEventListener('change', function() {\n            png_read_handler()\n        })\n        div.appendChild(file)\n        mod.file = file\n        //\n        // on-screen drawing canvas\n        //\n        var canvas = document.createElement('canvas')\n        canvas.width = mods.ui.canvas\n        canvas.height = mods.ui.canvas\n        canvas.style.backgroundColor = 'rgb(245,245,245)'\n        div.appendChild(canvas)\n        mod.canvas = canvas\n        div.appendChild(document.createElement('br'))\n        //\n        // off-screen image canvas\n        //\n        var canvas = document.createElement('canvas')\n        mod.img = canvas\n        //\n        // file select button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('select png file'))\n        btn.addEventListener('click', function() {\n            var file = document.getElementById(div.id + 'file_input')\n            file.value = null\n            file.click()\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // view button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('view'))\n        btn.addEventListener('click', function() {\n            var win = window.open('')\n            var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click', function() {\n                win.close()\n            })\n            win.document.body.appendChild(btn)\n            win.document.body.appendChild(document.createElement('br'))\n            var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n            var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, 0, 0)\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        // invert button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('invert'))\n        btn.addEventListener('click', function() {\n            invert_image()\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // flip button\n        //\n        var flip_btn = document.createElement('button')\n        flip_btn.style.padding = mods.ui.padding\n        flip_btn.style.margin = 1\n        flip_btn.appendChild(document.createTextNode('flip H'))\n        flip_btn.addEventListener('click', function() {\n            flip_image()\n        })\n        div.appendChild(flip_btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        //\n        // flipv button\n        //\n        var flipv_btn = document.createElement('button')\n        flipv_btn.style.padding = mods.ui.padding\n        flipv_btn.style.margin = 1\n        flipv_btn.appendChild(document.createTextNode('flip V'))\n        flipv_btn.addEventListener('click', function() {\n            flipv_image()\n        })\n        div.appendChild(flipv_btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        // rotate button\n        //\n        var rot_btn = document.createElement('button')\n        rot_btn.style.padding = mods.ui.padding\n        rot_btn.style.margin = 1\n        rot_btn.disabled = false\n        rot_btn.appendChild(document.createTextNode('rotate 90CW'))\n        rot_btn.addEventListener('click', function() {\n            rotate90cw_image()\n        })\n        div.appendChild(rot_btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // info div\n        //\n        var info = document.createElement('div')\n        info.setAttribute('id', div.id + 'info')\n        info.appendChild(document.createTextNode('dpi: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4 * mod.img.width / mod.dpi).toFixed(3) +\n                ' x ' + (25.4 * mod.img.height / mod.dpi).toFixed(3) + ' mm'\n            mod.intext.nodeValue = (mod.img.width / mod.dpi).toFixed(3) +\n                ' x ' + (mod.img.height / mod.dpi).toFixed(3) + ' in'\n            outputs.imageInfo.event()\n        })\n        info.appendChild(input)\n        mod.dpitext = input\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('px: ')\n        info.appendChild(text)\n        mod.pxtext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('mm: ')\n        info.appendChild(text)\n        mod.mmtext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('in: ')\n        info.appendChild(text)\n        mod.intext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('')\n        info.appendChild(text)\n        mod.name = text\n        div.appendChild(info)\n    }\n    //\n    // local functions\n    //\n    // read handler\n    //\n    function png_read_handler(event) {\n        var file_reader = new FileReader()\n        file_reader.onload = png_binary_handler\n        input_file = mod.file.files[0]\n        file_name = input_file.name\n        mod.name.nodeValue = file_name\n        file_reader.readAsArrayBuffer(input_file)\n    }\n    //\n    // binary load handler\n    //\n    function png_binary_handler(event) {\n        //\n        // get DPI\n        //\n        // 8 header\n        // 4 len, 4 type, data, 4 crc\n        // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n        // IEND\n        //\n        var units = ppx = ppy = 0\n        var buf = event.target.result\n        var view = new DataView(buf)\n        var ptr = 8\n        if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n            set_prompt(\"error: PNG header not found\")\n            return\n        }\n        while (1) {\n            var length = view.getUint32(ptr)\n            ptr += 4\n            var type = String.fromCharCode(\n                view.getUint8(ptr), view.getUint8(ptr + 1),\n                view.getUint8(ptr + 2), view.getUint8(ptr + 3))\n            ptr += 4\n            if (type == \"pHYs\") {\n                ppx = view.getUint32(ptr)\n                ppy = view.getUint32(ptr + 4)\n                units = view.getUint8(ptr + 8)\n            }\n            if (type == \"IEND\")\n                break\n            ptr += length + 4\n        }\n        if (units == 0) {\n            set_prompt(\"no PNG units not found, assuming 72 DPI\")\n            ppx = 72 * 1000 / 25.4\n        }\n        dpi = ppx * 25.4 / 1000\n        //\n        // read as URL for display\n        //\n        var file_reader = new FileReader()\n        file_reader.onload = png_URL_handler\n        file_reader.readAsDataURL(input_file)\n    }\n    //\n    // URL load handler\n    //\n    function png_URL_handler(event) {\n        var img = new Image()\n        img.setAttribute(\"src\", event.target.result)\n        img.onload = function() {\n            if (img.width > img.height) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - img.height / img.width)\n                var w = mod.canvas.width\n                var h = mod.canvas.width * img.height / img.width\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - img.width / img.height)\n                var y0 = 0\n                var w = mod.canvas.height * img.width / img.height\n                var h = mod.canvas.height\n            }\n\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.canvas.width = img.width\n            ctx.canvas.height = img.height\n            ctx.drawImage(img, 0, 0)\n\n            // Remove transparency\n            var imgData = ctx.getImageData(0, 0, mod.img.width, mod.img.height)\n            var data = imgData.data\n            for (var i = 3; i < data.length; i += 4) {\n                data[i] = 255;  // set alpha channel to opaque\n            }\n            ctx.putImageData(imgData, 0, 0)\n\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n            ctx.drawImage(mod.img, x0, y0, w, h)\n\n            mod.dpitext.value = dpi.toFixed(3)\n            mod.pxtext.nodeValue = img.width + ' x ' + img.height + ' px'\n            mod.mmtext.nodeValue = (25.4 * img.width / dpi).toFixed(3) +\n                ' x ' + (25.4 * img.height / dpi).toFixed(3) + ' mm'\n            mod.intext.nodeValue = (img.width / dpi).toFixed(3) +\n                ' x ' + (img.height / dpi).toFixed(3) + ' in'\n            outputs.image.event()\n            outputs.imageInfo.event()\n        }\n    }\n    //\n    // invert image\n    //\n    function invert_image() {\n        var blob = new Blob(['(' + worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer) //canvas pixels contain 4 elements: RGBA\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            for (var row = 0; row < h; ++row) {\n                for (var col = 0; col < w; ++col) {\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 0] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 0]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 1] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 1]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 2] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 2]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 3] = 255\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n    //\n    // flip image\n    //\n    function flip_image() {\n        var blob = new Blob(['(' + flip_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function flip_worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            // image stored as RGBA array first row 0 of w*4 elements followed by row 1... until row h-1\n            var index = 0  // index for the 1D array image\n            for (var row = 0; row < h; ++row) {\n                for (var col = 0; col < w / 2; ++col) {\n                    index = row * w * 4 + col * 4    //  given row,col coordinates it returns its index in the 1D array\n                    r_index = row* w * 4 + (w - 1 - col) * 4 // the index where this value should be\n                    // Replace RGB values\n                    temp_1 = buf[index + 0]   //R\n                    temp_2 = buf[index + 1]   //G\n                    temp_3 = buf[index + 2]   //B\n                    buf[index + 0] = buf[r_index + 0]  //R\n                    buf[index + 1] = buf[r_index + 1]  //G\n                    buf[index + 2] = buf[r_index + 2]  //B\n                    buf[index + 3] = 255  //A\n                    buf[r_index + 0] = temp_1  //R\n                    buf[r_index + 1] = temp_2  //G\n                    buf[r_index + 2] = temp_3  //B  \n                    buf[r_index + 3] = 255  //A\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n\n\n    //\n    // flip v image\n    //\n    function flipv_image() {\n        var blob = new Blob(['(' + flipv_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function flipv_worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var index = 0\n            for (var col = 0; col < w; ++col) {\n                for (var row = 0; row < h / 2; ++row) {\n                    index = row * w * 4 + col * 4\n                    r_index = (h - 1 - row) * w * 4 + col * 4\n                    temp_1 = buf[index + 0]\n                    temp_2 = buf[index + 1]\n                    temp_3 = buf[index + 2]\n                    buf[index + 0] = buf[r_index + 0]\n                    buf[index + 1] = buf[r_index + 1]\n                    buf[index + 2] = buf[r_index + 2]\n                    buf[r_index + 0] = temp_1\n                    buf[r_index + 1] = temp_2\n                    buf[r_index + 2] = temp_3\n                    buf[index + 3] = 255\n                    buf[r_index + 3] = 255\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n\n    //\n    // rotate image 90 CW by ChatGPT\n    //\n    function rotate90cw_worker() {\n        // by ChatGPT\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height;\n            var w = evt.data.width;\n            var img = new ImageData(new Uint8ClampedArray(evt.data.buffer), w, h);\n            var rotated = new ImageData(new Uint8ClampedArray(w * h * 4), h, w);\n    \n            for (var y = 0; y < h; y++) {\n                for (var x = 0; x < w; x++) {\n                    var srcPos = (y * w + x) * 4;\n                    var destPos = (x * h + (h - y - 1)) * 4;\n                    rotated.data[destPos + 0] = img.data[srcPos + 0];\n                    rotated.data[destPos + 1] = img.data[srcPos + 1];\n                    rotated.data[destPos + 2] = img.data[srcPos + 2];\n                    rotated.data[destPos + 3] = img.data[srcPos + 3];\n                }\n            }\n    \n            self.postMessage({\n                height: rotated.height,\n                width: rotated.width,\n                buffer: rotated.data.buffer\n            }, [rotated.data.buffer]);\n        });\n    }\n    \n\n\n    function rotate90cw_image() {\n        // by ChatGPT\n        var blob = new Blob(['(' + rotate90cw_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var canvas = mod.img\n            canvas.width = w\n            canvas.height = h\n            var ctx = canvas.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n            ctx.drawImage(canvas, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var canvas = mod.canvas\n        var ctx = canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, canvas.width, canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n         \n    //\n    // return values\n    //\n    return ({\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "371.8032420306854",
      "left": "210.8192350911063",
      "filename": "modules/read/png.js",
      "inputs": {},
      "outputs": {}
    },
    "0.037680464012921244": {
      "definition": "//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// Modified by Fran Sanchez June 2021 (add flip v) and ChatGPT Feb 23 (add rotate cw)\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'read png'\n    //\n    // initialization\n    //\n    var init = function() {}\n    //\n    // inputs\n    //\n    var inputs = {}\n    //\n    // outputs\n    //\n    var outputs = {\n        image: {\n            type: 'RGBA',\n            event: function() {\n                var ctx = mod.img.getContext(\"2d\")\n                var img = ctx.getImageData(0, 0, mod.img.width, mod.img.height)\n                mods.output(mod, 'image', img)\n            }\n        },\n        imageInfo: {\n            type: 'object',\n            event: function() {\n                var obj = {}\n                obj.name = mod.name.nodeValue\n                obj.dpi = parseFloat(mod.dpitext.value)\n                obj.width = mod.img.width\n                obj.height = mod.img.height\n                mods.output(mod, 'imageInfo', obj)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //\n        // file input control\n        //\n        var file = document.createElement('input')\n        file.setAttribute('type', 'file')\n        file.setAttribute('id', div.id + 'file_input')\n        file.style.position = 'absolute'\n        file.style.left = 0\n        file.style.top = 0\n        file.style.width = 0\n        file.style.height = 0\n        file.style.opacity = 0\n        file.addEventListener('change', function() {\n            png_read_handler()\n        })\n        div.appendChild(file)\n        mod.file = file\n        //\n        // on-screen drawing canvas\n        //\n        var canvas = document.createElement('canvas')\n        canvas.width = mods.ui.canvas\n        canvas.height = mods.ui.canvas\n        canvas.style.backgroundColor = 'rgb(245,245,245)'\n        div.appendChild(canvas)\n        mod.canvas = canvas\n        div.appendChild(document.createElement('br'))\n        //\n        // off-screen image canvas\n        //\n        var canvas = document.createElement('canvas')\n        mod.img = canvas\n        //\n        // file select button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('select png file'))\n        btn.addEventListener('click', function() {\n            var file = document.getElementById(div.id + 'file_input')\n            file.value = null\n            file.click()\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // view button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('view'))\n        btn.addEventListener('click', function() {\n            var win = window.open('')\n            var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click', function() {\n                win.close()\n            })\n            win.document.body.appendChild(btn)\n            win.document.body.appendChild(document.createElement('br'))\n            var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n            var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, 0, 0)\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        // invert button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('invert'))\n        btn.addEventListener('click', function() {\n            invert_image()\n        })\n        div.appendChild(btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // flip button\n        //\n        var flip_btn = document.createElement('button')\n        flip_btn.style.padding = mods.ui.padding\n        flip_btn.style.margin = 1\n        flip_btn.appendChild(document.createTextNode('flip H'))\n        flip_btn.addEventListener('click', function() {\n            flip_image()\n        })\n        div.appendChild(flip_btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        //\n        // flipv button\n        //\n        var flipv_btn = document.createElement('button')\n        flipv_btn.style.padding = mods.ui.padding\n        flipv_btn.style.margin = 1\n        flipv_btn.appendChild(document.createTextNode('flip V'))\n        flipv_btn.addEventListener('click', function() {\n            flipv_image()\n        })\n        div.appendChild(flipv_btn)\n        div.appendChild(document.createTextNode(' '))\n        //\n        // rotate button\n        //\n        var rot_btn = document.createElement('button')\n        rot_btn.style.padding = mods.ui.padding\n        rot_btn.style.margin = 1\n        rot_btn.disabled = false\n        rot_btn.appendChild(document.createTextNode('rotate 90CW'))\n        rot_btn.addEventListener('click', function() {\n            rotate90cw_image()\n        })\n        div.appendChild(rot_btn)\n        div.appendChild(document.createElement('br'))\n        //\n        // info div\n        //\n        var info = document.createElement('div')\n        info.setAttribute('id', div.id + 'info')\n        info.appendChild(document.createTextNode('dpi: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input', function() {\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4 * mod.img.width / mod.dpi).toFixed(3) +\n                ' x ' + (25.4 * mod.img.height / mod.dpi).toFixed(3) + ' mm'\n            mod.intext.nodeValue = (mod.img.width / mod.dpi).toFixed(3) +\n                ' x ' + (mod.img.height / mod.dpi).toFixed(3) + ' in'\n            outputs.imageInfo.event()\n        })\n        info.appendChild(input)\n        mod.dpitext = input\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('px: ')\n        info.appendChild(text)\n        mod.pxtext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('mm: ')\n        info.appendChild(text)\n        mod.mmtext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('in: ')\n        info.appendChild(text)\n        mod.intext = text\n        info.appendChild(document.createElement('br'))\n        var text = document.createTextNode('')\n        info.appendChild(text)\n        mod.name = text\n        div.appendChild(info)\n    }\n    //\n    // local functions\n    //\n    // read handler\n    //\n    function png_read_handler(event) {\n        var file_reader = new FileReader()\n        file_reader.onload = png_binary_handler\n        input_file = mod.file.files[0]\n        file_name = input_file.name\n        mod.name.nodeValue = file_name\n        file_reader.readAsArrayBuffer(input_file)\n    }\n    //\n    // binary load handler\n    //\n    function png_binary_handler(event) {\n        //\n        // get DPI\n        //\n        // 8 header\n        // 4 len, 4 type, data, 4 crc\n        // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n        // IEND\n        //\n        var units = ppx = ppy = 0\n        var buf = event.target.result\n        var view = new DataView(buf)\n        var ptr = 8\n        if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n            set_prompt(\"error: PNG header not found\")\n            return\n        }\n        while (1) {\n            var length = view.getUint32(ptr)\n            ptr += 4\n            var type = String.fromCharCode(\n                view.getUint8(ptr), view.getUint8(ptr + 1),\n                view.getUint8(ptr + 2), view.getUint8(ptr + 3))\n            ptr += 4\n            if (type == \"pHYs\") {\n                ppx = view.getUint32(ptr)\n                ppy = view.getUint32(ptr + 4)\n                units = view.getUint8(ptr + 8)\n            }\n            if (type == \"IEND\")\n                break\n            ptr += length + 4\n        }\n        if (units == 0) {\n            set_prompt(\"no PNG units not found, assuming 72 DPI\")\n            ppx = 72 * 1000 / 25.4\n        }\n        dpi = ppx * 25.4 / 1000\n        //\n        // read as URL for display\n        //\n        var file_reader = new FileReader()\n        file_reader.onload = png_URL_handler\n        file_reader.readAsDataURL(input_file)\n    }\n    //\n    // URL load handler\n    //\n    function png_URL_handler(event) {\n        var img = new Image()\n        img.setAttribute(\"src\", event.target.result)\n        img.onload = function() {\n            if (img.width > img.height) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - img.height / img.width)\n                var w = mod.canvas.width\n                var h = mod.canvas.width * img.height / img.width\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - img.width / img.height)\n                var y0 = 0\n                var w = mod.canvas.height * img.width / img.height\n                var h = mod.canvas.height\n            }\n\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.canvas.width = img.width\n            ctx.canvas.height = img.height\n            ctx.drawImage(img, 0, 0)\n\n            // Remove transparency\n            var imgData = ctx.getImageData(0, 0, mod.img.width, mod.img.height)\n            var data = imgData.data\n            for (var i = 3; i < data.length; i += 4) {\n                data[i] = 255;  // set alpha channel to opaque\n            }\n            ctx.putImageData(imgData, 0, 0)\n\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n            ctx.drawImage(mod.img, x0, y0, w, h)\n\n            mod.dpitext.value = dpi.toFixed(3)\n            mod.pxtext.nodeValue = img.width + ' x ' + img.height + ' px'\n            mod.mmtext.nodeValue = (25.4 * img.width / dpi).toFixed(3) +\n                ' x ' + (25.4 * img.height / dpi).toFixed(3) + ' mm'\n            mod.intext.nodeValue = (img.width / dpi).toFixed(3) +\n                ' x ' + (img.height / dpi).toFixed(3) + ' in'\n            outputs.image.event()\n            outputs.imageInfo.event()\n        }\n    }\n    //\n    // invert image\n    //\n    function invert_image() {\n        var blob = new Blob(['(' + worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer) //canvas pixels contain 4 elements: RGBA\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            for (var row = 0; row < h; ++row) {\n                for (var col = 0; col < w; ++col) {\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 0] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 0]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 1] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 1]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 2] = 255 - buf[(h - 1 - row) * w * 4 + col * 4 + 2]\n                    buf[(h - 1 - row) * w * 4 + col * 4 + 3] = 255\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n    //\n    // flip image\n    //\n    function flip_image() {\n        var blob = new Blob(['(' + flip_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function flip_worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            // image stored as RGBA array first row 0 of w*4 elements followed by row 1... until row h-1\n            var index = 0  // index for the 1D array image\n            for (var row = 0; row < h; ++row) {\n                for (var col = 0; col < w / 2; ++col) {\n                    index = row * w * 4 + col * 4    //  given row,col coordinates it returns its index in the 1D array\n                    r_index = row* w * 4 + (w - 1 - col) * 4 // the index where this value should be\n                    // Replace RGB values\n                    temp_1 = buf[index + 0]   //R\n                    temp_2 = buf[index + 1]   //G\n                    temp_3 = buf[index + 2]   //B\n                    buf[index + 0] = buf[r_index + 0]  //R\n                    buf[index + 1] = buf[r_index + 1]  //G\n                    buf[index + 2] = buf[r_index + 2]  //B\n                    buf[index + 3] = 255  //A\n                    buf[r_index + 0] = temp_1  //R\n                    buf[r_index + 1] = temp_2  //G\n                    buf[r_index + 2] = temp_3  //B  \n                    buf[r_index + 3] = 255  //A\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n\n\n    //\n    // flip v image\n    //\n    function flipv_image() {\n        var blob = new Blob(['(' + flipv_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n\n    function flipv_worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var index = 0\n            for (var col = 0; col < w; ++col) {\n                for (var row = 0; row < h / 2; ++row) {\n                    index = row * w * 4 + col * 4\n                    r_index = (h - 1 - row) * w * 4 + col * 4\n                    temp_1 = buf[index + 0]\n                    temp_2 = buf[index + 1]\n                    temp_3 = buf[index + 2]\n                    buf[index + 0] = buf[r_index + 0]\n                    buf[index + 1] = buf[r_index + 1]\n                    buf[index + 2] = buf[r_index + 2]\n                    buf[r_index + 0] = temp_1\n                    buf[r_index + 1] = temp_2\n                    buf[r_index + 2] = temp_3\n                    buf[index + 3] = 255\n                    buf[r_index + 3] = 255\n                }\n            }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n\n    //\n    // rotate image 90 CW by ChatGPT\n    //\n    function rotate90cw_worker() {\n        // by ChatGPT\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height;\n            var w = evt.data.width;\n            var img = new ImageData(new Uint8ClampedArray(evt.data.buffer), w, h);\n            var rotated = new ImageData(new Uint8ClampedArray(w * h * 4), h, w);\n    \n            for (var y = 0; y < h; y++) {\n                for (var x = 0; x < w; x++) {\n                    var srcPos = (y * w + x) * 4;\n                    var destPos = (x * h + (h - y - 1)) * 4;\n                    rotated.data[destPos + 0] = img.data[srcPos + 0];\n                    rotated.data[destPos + 1] = img.data[srcPos + 1];\n                    rotated.data[destPos + 2] = img.data[srcPos + 2];\n                    rotated.data[destPos + 3] = img.data[srcPos + 3];\n                }\n            }\n    \n            self.postMessage({\n                height: rotated.height,\n                width: rotated.width,\n                buffer: rotated.data.buffer\n            }, [rotated.data.buffer]);\n        });\n    }\n    \n\n\n    function rotate90cw_image() {\n        // by ChatGPT\n        var blob = new Blob(['(' + rotate90cw_worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = evt.data.height\n            var w = evt.data.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf, w, h)\n            var canvas = mod.img\n            canvas.width = w\n            canvas.height = h\n            var ctx = canvas.getContext(\"2d\")\n            ctx.putImageData(imgdata, 0, 0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n            ctx.drawImage(canvas, x0, y0, wd, hd)\n            webworker.terminate()\n            outputs.image.event()\n        })\n        var canvas = mod.canvas\n        var ctx = canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, canvas.width, canvas.height)\n        var h = mod.img.height\n        var w = mod.img.width\n        var ctx = mod.img.getContext(\"2d\")\n        var img = ctx.getImageData(0, 0, w, h)\n        webworker.postMessage({\n            height: img.height,\n            width: img.width,\n            buffer: img.data.buffer\n        }, [img.data.buffer])\n    }\n         \n    //\n    // return values\n    //\n    return ({\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "1119.7483350037314",
      "left": "202.59190454033524",
      "filename": "modules/read/png.js",
      "inputs": {},
      "outputs": {}
    },
    "0.7353074975250568": {
      "definition": "(function() {\n    var mod = {}\n    var name = 'PCB Price Calculator'\n    mod.area = 0\n\n    var init = function() {\n        mod.rate_area.value = '2.0'     // Set your default Rate here\n        mod.setup.value = '50'          // Set your default Base Fee here\n        mod.rate_time.value = '300'     // Set your default Hourly Rate here (₹/hour)\n        mod.time_minutes.value = '0'    // Estimated time in minutes\n        mod.quantity.value = '1'        // Number of PCBs\n    }\n\n    var update_side = function() {\n        if (mod.single.checked) {\n            mod.rate_area.value = '2.0'\n        } else if (mod.double.checked) {\n            mod.rate_area.value = '3.5'\n        }\n        update_price()\n    }\n\n    var update_price = function() {\n        var rA = parseFloat(mod.rate_area.value) || 0\n        var s = parseFloat(mod.setup.value) || 0\n        var rT = parseFloat(mod.rate_time.value) || 0\n        var t = parseFloat(mod.time_minutes.value) || 0\n        var qty = parseFloat(mod.quantity.value) || 1\n        \n        // Calculate time cost (convert minutes to hours)\n        var timeCost = (t / 60) * rT\n        \n        // Total per unit: Area cost + Base Fee + Time cost\n        var perUnit = (mod.area * rA) + s + timeCost\n        var total = perUnit * qty\n        \n        // Apply rounding if checkbox is checked\n        if (mod.roundoff && mod.roundoff.checked) {\n            perUnit = Math.round(perUnit)\n            total = Math.round(total)\n        }\n        \n        var decimals = mod.roundoff && mod.roundoff.checked ? 0 : 2\n        mod.display_area.nodeValue = 'Area: ' + mod.area.toFixed(2) + ' cm²'\n        mod.display_time.nodeValue = 'Time: ' + t.toFixed(0) + ' min (' + (t/60).toFixed(2) + ' hrs)'\n        mod.display_quantity.nodeValue = 'Quantity: ' + qty\n        mod.display_perunit.nodeValue = 'Per Unit: ₹' + perUnit.toFixed(decimals)\n        mod.display_price.nodeValue = 'Total Price: ₹' + total.toFixed(decimals)\n        mods.fit(mod.div) \n    }\n\n    var inputs = {\n        imageInfo: {\n            type: 'object',\n            event: function(evt) {\n                var dpi = evt.detail.dpi\n                var w = (evt.detail.width / dpi) * 2.54\n                var h = (evt.detail.height / dpi) * 2.54\n                mod.area = w * h\n                update_price()\n            }\n        }\n    }\n\n    var interface = function(div) {\n        mod.div = div\n        div.style.textAlign = 'left'\n        div.style.padding = '10px'\n        div.style.minWidth = '160px'\n        \n        var createInput = function(label) {\n            div.appendChild(document.createTextNode(label))\n            var i = document.createElement('input')\n            i.type = 'text'; i.size = 4; i.style.margin = '3px'\n            i.addEventListener('input', update_price)\n            div.appendChild(i)\n            div.appendChild(document.createElement('br'))\n            return i\n        }\n\n        // PCB Side Selection (creates hidden input for rate_area)\n        mod.rate_area = document.createElement('input')\n        mod.rate_area.type = 'hidden'\n        mod.rate_area.value = '2.0'\n        div.appendChild(mod.rate_area)\n        \n        div.appendChild(document.createTextNode('PCB Type: '))\n        div.appendChild(document.createElement('br'))\n        var single = document.createElement('input')\n        single.type = 'radio'\n        single.name = 'pcb_side'\n        single.id = 'single'\n        single.checked = true\n        single.addEventListener('change', update_side)\n        div.appendChild(single)\n        div.appendChild(document.createTextNode(' Single (₹2/cm²)'))\n        div.appendChild(document.createElement('br'))\n        mod.single = single\n        \n        var double = document.createElement('input')\n        double.type = 'radio'\n        double.name = 'pcb_side'\n        double.id = 'double'\n        double.addEventListener('change', update_side)\n        div.appendChild(double)\n        div.appendChild(document.createTextNode(' Double (₹3.5/cm²)'))\n        mod.double = double\n        div.appendChild(document.createElement('br'))\n\n        mod.setup = createInput('Base Fee (₹): ')\n        mod.rate_time = createInput('Hourly Rate (₹/hr): ')\n        mod.time_minutes = createInput('Time (minutes): ')\n        mod.quantity = createInput('Number of PCBs: ')\n        \n        div.appendChild(document.createElement('br'))\n        var checkbox = document.createElement('input')\n        checkbox.type = 'checkbox'\n        checkbox.id = 'roundoff'\n        checkbox.checked = true\n        checkbox.addEventListener('change', update_price)\n        div.appendChild(checkbox)\n        div.appendChild(document.createTextNode(' Round off to nearest ₹'))\n        mod.roundoff = checkbox\n        \n        div.appendChild(document.createElement('hr'))\n        \n        var areaDiv = document.createElement('div')\n        mod.display_area = document.createTextNode('Area: --')\n        areaDiv.appendChild(mod.display_area); div.appendChild(areaDiv)\n        \n        var timeDiv = document.createElement('div')\n        mod.display_time = document.createTextNode('Time: --')\n        timeDiv.appendChild(mod.display_time); div.appendChild(timeDiv)\n        \n        var qtyDiv = document.createElement('div')\n        mod.display_quantity = document.createTextNode('Quantity: 1')\n        qtyDiv.appendChild(mod.display_quantity); div.appendChild(qtyDiv)\n        \n        var perunitDiv = document.createElement('div')\n        mod.display_perunit = document.createTextNode('Per Unit: ₹0.00')\n        perunitDiv.appendChild(mod.display_perunit); div.appendChild(perunitDiv)\n        \n        var priceDiv = document.createElement('div')\n        priceDiv.style.fontSize = '1.3em'; priceDiv.style.color = '#1b5e20'; priceDiv.style.fontWeight = 'bold'\n        mod.display_price = document.createTextNode('Total Price: ₹0.00')\n        priceDiv.appendChild(mod.display_price); div.appendChild(priceDiv)\n    }\n\n    return {\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: {},\n        interface: interface\n    }\n}())",
      "top": "1628.6482876962962",
      "left": "632.7106677072845",
      "filename": "modules/ui/label.js",
      "inputs": {},
      "outputs": {}
    },
    "0.49202738148831227": {
      "definition": "//\n// image tabs \n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2023\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'image tabs'\n    //\n    // initialization\n    //\n    var init = function() {\n      mod.length_mm.value = 5\n      mod.length_in.value = parseFloat(mod.length_mm.value) / 25.4\n      mod.width_mm.value = 0.9\n      mod.width_in.value = parseFloat(mod.width_mm.value) / 25.4\n      mod.E.checked = false\n      mod.W.checked = false\n      //mod.N.checked = false // NS path bug?\n      //mod.S.checked = false\n      }\n    //\n    // inputs\n    //\n    var inputs = {\n        image: {\n            type: 'RGBA',\n            event: function(evt) {\n                mod.input = evt.detail\n                var ctx = mod.img.getContext(\"2d\")\n                ctx.canvas.width = mod.input.width\n                ctx.canvas.height = mod.input.height\n                ctx.putImageData(mod.input, 0, 0)\n                image_tabs()\n                }\n            },\n        imageInfo: {\n            type: 'object',\n            event: function(evt) {\n                mod.name = evt.detail.name\n                mod.dpi = evt.detail.dpi\n                mod.width = evt.detail.width\n                mod.height = evt.detail.height\n                image_tabs()\n                // race condition, assuming imageInfo comes 2nd\n                }\n            },\n        settings: {\n            type: 'object',\n            event: function(evt) {\n                set_values(evt.detail)\n                image_tabs()\n                }\n            }\n        }\n    //\n    // outputs\n    //\n    var outputs = {\n        image: {\n            type: 'RGBA',\n            event: function() {\n                var ctx = mod.img.getContext(\"2d\")\n                var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n                mods.output(mod,'image',img)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n        //\n        // on-screen drawing canvas\n        //\n        var canvas = document.createElement('canvas')\n        canvas.width = mods.ui.canvas\n        canvas.height = mods.ui.canvas\n        canvas.style.backgroundColor = 'rgb(255,255,255)'\n        div.appendChild(canvas)\n        mod.canvas = canvas\n        div.appendChild(document.createElement('br'))\n        //\n        // off-screen image canvas\n        //\n        var canvas = document.createElement('canvas')\n        mod.img = canvas\n        //\n        // tab width\n        //\n        div.appendChild(document.createTextNode('tab width'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('mm: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input',function() {\n            mod.width_in.value = parseFloat(mod.width_mm.value)/25.4\n            image_tabs()\n        })\n        div.appendChild(input)\n        mod.width_mm = input\n        div.appendChild(document.createTextNode(' in: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input',function() {\n            mod.width_mm.value = parseFloat(mod.width_in.value)*25.4\n            image_tabs()\n        })\n        div.appendChild(input)\n        mod.width_in = input\n        div.appendChild(document.createElement('br'))\n        //\n        // tab length\n        //\n        div.appendChild(document.createTextNode('tab length'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('mm: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input',function() {\n            mod.length_in.value = parseFloat(mod.length_mm.value)/25.4\n            image_tabs()\n        })\n        div.appendChild(input)\n        mod.length_mm = input\n        div.appendChild(document.createTextNode(' in: '))\n        var input = document.createElement('input')\n        input.type = 'text'\n        input.size = 6\n        input.addEventListener('input',function() {\n            mod.length_mm.value = parseFloat(mod.length_in.value)*25.4\n            image_tabs()\n        })\n        div.appendChild(input)\n        mod.length_in = input\n        div.appendChild(document.createElement('br'))\n        //\n        // tab sides\n        //\n        div.appendChild(document.createTextNode('tab sides: '))\n        div.appendChild(document.createTextNode('E'))\n        var input = document.createElement('input')\n        input.type = 'checkbox'\n        input.addEventListener('change',function() {\n            image_tabs()\n            })\n        div.appendChild(input)\n        mod.E = input\n        div.appendChild(document.createTextNode(' W'))\n        var input = document.createElement('input')\n        input.type = 'checkbox'\n        input.addEventListener('change',function() {\n            image_tabs()\n            })\n        div.appendChild(input)\n        mod.W = input\n        /*\n        div.appendChild(document.createTextNode(' N'))\n        var input = document.createElement('input')\n        input.type = 'checkbox'\n        input.addEventListener('change',function() {\n            image_tabs()\n            })\n        div.appendChild(input)\n        mod.N = input\n        div.appendChild(document.createTextNode(' S'))\n        var input = document.createElement('input')\n        input.type = 'checkbox'\n        input.addEventListener('change',function() {\n            image_tabs()\n            })\n        div.appendChild(input)\n        mod.S = input\n        */\n        div.appendChild(document.createElement('br'))\n        //\n        // view button\n        //\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        btn.appendChild(document.createTextNode('view'))\n        btn.addEventListener('click', function() {\n            var win = window.open('')\n            var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click', function() {\n                win.close()\n            })\n            win.document.body.appendChild(btn)\n            win.document.body.appendChild(document.createElement('br'))\n            var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n            var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img, 0, 0)\n        })\n        div.appendChild(btn)\n    }\n    //\n    // local functions\n    //\n    // set_values\n    //\n    function set_values(settings) {\n        for (var s in settings) {\n            switch (s) {\n                case 'tab width (in)':\n                    mod.width_in.value = settings[s]\n                    mod.width_mm.value = parseFloat(mod.width_in.value)*25.4\n                    break\n                case 'tab width (mm)':\n                    mod.width_mm.value = settings[s]\n                    mod.width_in.value = parseFloat(mod.width_mm.value)/25.4\n                    break\n                case 'tab length (in)':\n                    mod.length_in.value = settings[s]\n                    mod.length_mm.value = parseFloat(mod.length_in.value)*25.4\n                    break\n                case 'tab length (mm)':\n                    mod.length_mm.value = settings[s]\n                    mod.length_in.value = parseFloat(mod.length_mm.value)/25.4\n                    break\n                }\n            }\n        }\n    //\n    // image tabs\n    //\n    function image_tabs() {\n        var blob = new Blob(['(' + worker.toString() + '())'])\n        var url = window.URL.createObjectURL(blob)\n        var webworker = new Worker(url)\n        webworker.addEventListener('message', function(evt) {\n            window.URL.revokeObjectURL(url)\n            var h = mod.img.height\n            var w = mod.img.width\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            var imgdata = new ImageData(buf,w,h)\n            var ctx = mod.img.getContext(\"2d\")\n            ctx.putImageData(imgdata,0,0)\n            if (w > h) {\n                var x0 = 0\n                var y0 = mod.canvas.height * .5 * (1 - h / w)\n                var wd = mod.canvas.width\n                var hd = mod.canvas.width * h / w\n            } else {\n                var x0 = mod.canvas.width * .5 * (1 - w / h)\n                var y0 = 0\n                var wd = mod.canvas.height * w / h\n                var hd = mod.canvas.height\n            }\n            var ctx = mod.canvas.getContext(\"2d\")\n            ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n            ctx.drawImage(mod.img,x0,y0,wd,hd)\n            webworker.terminate()\n            outputs.image.event()\n            })\n        var ctx = mod.canvas.getContext(\"2d\")\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\n        var E = parseFloat(mod.E.value)\n        var ctx = mod.img.getContext(\"2d\")\n        ctx.putImageData(mod.input,0,0)\n        var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n        webworker.postMessage({\n            height: mod.input.height,\n            width: mod.input.width,\n            E: mod.E.checked,\n            W: mod.W.checked,\n            //N: mod.N.checked,\n            //S: mod.S.checked,\n            dpi: mod.dpi,\n            lenin: mod.length_in.value,\n            widin: mod.width_in.value,\n            buffer: img.data.buffer\n            }, [img.data.buffer])\n        }\n\n    function worker() {\n        self.addEventListener('message', function(evt) {\n            var h = evt.data.height\n            var w = evt.data.width\n            console.log(evt.data.lenin,evt.data.dpi)\n            var dl = (evt.data.lenin*evt.data.dpi)\n            var dw = (evt.data.widin*evt.data.dpi)\n            var E = evt.data.E\n            var W = evt.data.W\n            //var N = evt.data.N\n            //var S = evt.data.S\n            var buf = new Uint8ClampedArray(evt.data.buffer)\n            console.log(evt.data.widin,dl,dw)\n            for (var row = 0; row < h; ++row) {\n                for (var col = 0; col < w; ++col) {\n                   if (E) {\n                      if ((row > (h/2-dw/2)) && (row < (h/2+dw/2))\n                            && (col > (w-dl))) {\n                         buf[(h-1-row)*w*4+col*4+0] = 255\n                         buf[(h-1-row)*w*4+col*4+1] = 255\n                         buf[(h-1-row)*w*4+col*4+2] = 255\n                         buf[(h-1-row)*w*4+col*4+3] = 255\n                         }\n                      }\n                   if (W) {\n                      if ((row > (h/2-dw/2)) && (row < (h/2+dw/2))\n                            && (col < dl)) {\n                         buf[(h-1-row)*w*4+col*4+0] = 255\n                         buf[(h-1-row)*w*4+col*4+1] = 255\n                         buf[(h-1-row)*w*4+col*4+2] = 255\n                         buf[(h-1-row)*w*4+col*4+3] = 255\n                         }\n                      }\n                   /*\n                   if (N) {\n                      if ((col > (w/2-dw/2)) && (col < (w/2+dw/2))\n                            && (row > (h-dl))) {\n                         buf[(h-1-row)*w*4+col*4+0] = 255\n                         buf[(h-1-row)*w*4+col*4+1] = 255\n                         buf[(h-1-row)*w*4+col*4+2] = 255\n                         buf[(h-1-row)*w*4+col*4+3] = 255\n                         }\n                      }\n                   if (S) {\n                      if ((col > (w/2-dw/2)) && (col < (w/2+dw/2))\n                            && (row < dl)) {\n                         buf[(h-1-row)*w*4+col*4+0] = 255\n                         buf[(h-1-row)*w*4+col*4+1] = 255\n                         buf[(h-1-row)*w*4+col*4+2] = 255\n                         buf[(h-1-row)*w*4+col*4+3] = 255\n                         }\n                      }\n                   */\n                   }\n               }\n            self.postMessage({\n                buffer: buf.buffer\n            }, [buf.buffer])\n        })\n    }\n    //\n    // return values\n    //\n    return ({\n        mod: mod,\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())\n",
      "top": "1436.7946551126718",
      "left": "1016.7429950780202",
      "filename": "modules/image/tabs.js",
      "inputs": {},
      "outputs": {}
    },
    "0.3947110891103697": {
      "definition": "//\n// multi_toolpath to G-code\n//\n// Neil Gershenfeld & Quentin Bolsee\n// (c) Massachusetts Institute of Technology 2024\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// Updated: Quentin Bolsee\n// Date: Oct 6 2024\n// Comments: multi-tool\n//\n// Updated: Quentin Bolsee\n// Date: Oct 26 2023\n// Comments: time estimate\n//\n// Updated: Neil Gershenfeld\n// Date: Aug 26 2023\n// Comments: compatibility tweaks\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// Updated: Steven Chew\n// Date:     Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//              - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// closure\n//\n(function(){\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'multi_toolpath to G-code'\n    //\n    // initialization\n    //\n    var init = function() {\n        mod.cutspeed.value = '8'\n        mod.plungespeed.value = '3'\n        mod.jogheight.value = '1'\n        mod.spindlespeed.value = '12000'\n        mod.dwell.value = '0'\n        mod.coolantoff.checked = true\n        mod.formatMm.checked = true\n        }\n    //\n    // inputs\n    //\n    var inputs = {\n        toolpaths: {\n            type: 'object',\n            event: function(evt) {\n                var t = evt.detail\n                mod.paths = t.paths\n                mod.tools = t.tools\n                mod.name = t.name\n                mod.dpi = t.dpi\n                mod.width = t.width\n                mod.height = t.height\n                mod.depth = t.depth\n                make_path()\n            }\n        }\n    }\n    //\n    // outputs\n    //\n    var outputs = {\n        file: {\n            type:'',\n            event: function(str){\n                obj = {}\n                obj.name = mod.name+\".nc\"\n                obj.contents = str\n                mods.output(mod,'file',obj)\n            }\n        }\n    }\n    //\n    // interface\n    //\n    var interface = function(div){\n        mod.div = div\n        //\n        // cut speed\n        //\n        div.appendChild(document.createTextNode('cut speed: '))\n        var input = document.createElement('input')\n            input.type = 'text'\n            input.size = 6\n            div.appendChild(input)\n            mod.cutspeed = input\n        div.appendChild(document.createTextNode(' (mm/s)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // plunge speed\n        //\n        div.appendChild(document.createTextNode('plunge speed: '))\n        var input = document.createElement('input')\n            input.type = 'text'\n            input.size = 6\n            div.appendChild(input)\n            mod.plungespeed = input\n        div.appendChild(document.createTextNode(' (mm/s)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // jog height\n        //\n        div.appendChild(document.createTextNode('jog height: '))\n        var input = document.createElement('input')\n            input.type = 'text'\n            input.size = 6\n            div.appendChild(input)\n            mod.jogheight = input\n        div.appendChild(document.createTextNode(' (mm)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // spindle speed\n        //\n        div.appendChild(document.createTextNode('spindle: '))\n        var input = document.createElement('input')\n            input.type = 'text'\n            input.size = 6\n            div.appendChild(input)\n            mod.spindlespeed = input\n        div.appendChild(document.createTextNode(' (RPM)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // dwell\n        //\n        div.appendChild(document.createTextNode('dwell: '))\n        var input = document.createElement('input')\n            input.type = 'text'\n            input.size = 6\n            div.appendChild(input)\n            mod.dwell = input\n        div.appendChild(document.createTextNode(' (0:skip)'))\n        div.appendChild(document.createElement('br'))\n        //\n        // coolant\n        //\n        div.appendChild(document.createTextNode('coolant:'))\n        var input = document.createElement('input')\n            input.type = 'radio'\n            input.name = mod.div.id+'coolant'\n            input.id = mod.div.id+'coolanton'\n            div.appendChild(input)\n            mod.coolanton = input\n        div.appendChild(document.createTextNode('on'))\n        var input = document.createElement('input')\n            input.type = 'radio'\n            input.name = mod.div.id+'coolant'\n            input.id = mod.div.id+'coolantoff'\n            div.appendChild(input)\n            mod.coolantoff = input\n        div.appendChild(document.createTextNode('off'))\n        div.appendChild(document.createElement('br'))\n        //\n        // inch or mm format\n        //\n        div.appendChild(document.createTextNode('format:'))\n        var input = document.createElement('input')\n            input.type = 'radio'\n            input.name = mod.div.id+'format'\n            input.id = mod.div.id+'formatInch'\n            input.checked = true\n            div.appendChild(input)\n            mod.formatInch = input\n        div.appendChild(document.createTextNode('inch'))\n        var input = document.createElement('input')\n            input.type = 'radio'\n            input.name = mod.div.id+'format'\n            input.id = mod.div.id+'formatMm'\n            div.appendChild(input)\n            mod.formatMm = input\n        div.appendChild(document.createTextNode('mm'))\n        div.appendChild(document.createElement('br'))\n        //\n        // time\n        //\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createTextNode('Estimated time: '))\n        var timeTxt = document.createTextNode('--:--:--')\n        div.appendChild(timeTxt)\n        mod.timeTxt = timeTxt\n        div.appendChild(document.createElement('br'))\n        }\n    //\n    // local functions\n    //\n    function toHHMMSS(sec_num) {\n      sec_num = Math.floor(sec_num)\n      var hours    = Math.floor(sec_num / 3600);\n      var minutes = Math.floor((sec_num - (hours * 3600)) / 60);\n      var seconds = sec_num - (hours * 3600) - (minutes * 60);\n\n      if (hours    < 10) {hours    = \"0\"+hours;}\n      if (minutes < 10) {minutes = \"0\"+minutes;}\n      if (seconds < 10) {seconds = \"0\"+seconds;}\n      return hours+':'+minutes+':'+seconds;\n      }\n    function make_path() {\n        var total_minutes = 0.0 // in minutes\n        var dx = 25.4*mod.width/mod.dpi\n        var cut_speed = parseFloat(mod.cutspeed.value)\n        var plunge_speed = parseFloat(mod.plungespeed.value)\n        var jog_height = parseFloat(mod.jogheight.value)\n        var nx = mod.width\n        var scale = dx/(nx-1)\n        var in_mm_scale = 1\n        if (mod.formatInch.checked) {\n            dx /= 25.4\n            scale /= 25.4\n            cut_speed /= 25.4\n            plunge_speed /= 25.4\n            jog_height /= 25.4\n            }\n        var spindle_speed = parseFloat(mod.spindlespeed.value)\n        var dwell = parseFloat(mod.dwell.value)\n        str = \"%\\n\" // tape start\n        str += \"G17\\n\" // xy plane\n        if (mod.formatInch.checked)\n            str += \"G20\\n\" // inches\n        if (mod.formatMm.checked)\n            str += \"G21\\n\" // mm\n        str += \"G40\\n\" // cancel tool radius compensation\n        str += \"G49\\n\" // cancel tool length compensation\n        str += \"G54\\n\" // coordinate system 1\n        str += \"G80\\n\" // cancel canned cycles\n        str += \"G90\\n\" // absolute coordinates\n        str += \"G94\\n\" // feed/minute units\n        cut_speed *= 60 // feed/sec -> /minute units\n        plunge_speed *= 60 // feed/sec -> /minute units\n        //\n        // simulate tool motion to estimate time\n        //\n        var toolxyz = {\n            xp: null,\n            yp: null,\n            zp: null,\n            move: function(xn, yn, zn) {\n              var dist = 0\n              if ((this.xp !== null) && (this.yp !== null) && (this.zp !== null)) {\n                 dist = Math.sqrt((xn-this.xp)**2+(yn-this.yp)**2+(zn-this.zp)**2)\n                 }\n              this.xp = xn\n              this.yp = yn\n              this.zp = zn\n              return dist\n              }\n            }\n        for (var i = 0; i < mod.paths.length; i++) {\n            str += `T${mod.tools[i]} M06\\n` // tool change\n            str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n            str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n            if (mod.coolanton.checked)\n                str += \"M08\\n\" // coolant on\n            str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n            str += \"M03\\n\" // spindle on clockwise\n            if (dwell != 0)\n                str += \"G04 P\"+dwell+\"\\n\" // give spindle time to spin up\n\n            var path = mod.paths[i];\n            //\n            // follow segments\n            //\n            for (var seg = 0; seg < path.length; ++seg) {\n                //\n                // move up to starting point\n                //\n                x = scale*path[seg][0][0]\n                y = scale*path[seg][0][1]\n                z = jog_height\n                str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n                str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n                total_minutes += toolxyz.move(x, y, z) / cut_speed\n                //\n                // move down\n                //\n                z = scale*path[seg][0][2]\n                str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n                str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n                for (var pt = 1; pt < path[seg].length; ++pt) {\n                    //\n                    // move to next point\n                    //\n                    x = scale*path[seg][pt][0]\n                    y = scale*path[seg][pt][1]\n                    z = scale*path[seg][pt][2]\n                    str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n                    total_minutes += toolxyz.move(x, y, z) / cut_speed\n                    }\n                }\n            //\n            // finish\n            //\n            z = jog_height\n            str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up\n            str += \"G00X0.0000Y0.0000\"+\"Z\"+jog_height.toFixed(4)+\"\\n\" // finish at origin\n            str += \"M05\\n\" // spindle stop\n            total_minutes += toolxyz.move(x, y, z) / cut_speed\n            if (mod.coolanton.checked)\n                str += \"M09\\n\" // coolant off\n        }\n        str += \"M30\\n\" // program end and reset\n        str += \"%\\n\" // tape end\n        //\n        // print time\n        //\n        mod.timeTxt.textContent = toHHMMSS(total_minutes * 60)\n        //\n        // output file\n        //\n        outputs.file.event(str)\n        }\n    //\n    // return values\n    //\n    return ({\n        mod:mod,\n        name:name,\n        init:init,\n        inputs:inputs,\n        outputs:outputs,\n        interface:interface\n        })\n    }())\n",
      "top": "1520.6665239647239",
      "left": "3233.5215403492375",
      "filename": "modules/10%20toolpath/20%20formats/g-code.js",
      "inputs": {},
      "outputs": {}
    },
    "0.06233643688719437": {
      "definition": "//\n// mill with V Bit\n//\n// Sibin k s\n//\n// (c) Massachusetts Institute of Technology 2024\n//\n//\n\n\n\n(function() {\n    var mod = {}\n    var name = 'V-Bit Calculator'\n    var init = function() {\n        mod.tool_num.value = '2'\n        mod.angle.value = '30'\n        mod.tip_dia.value = '0.35'\n        mod.cut_depth.value = '0.09'\n        mod.max_depth.value = '0.09'\n        outputs.settings.event() // Initialize diagram values\n    }\n    var inputs = {}\n    var outputs = {\n        settings: {\n            type: 'object',\n            event: function() {\n                var angle_rad = (parseFloat(mod.angle.value) / 2) * (Math.PI / 180)\n                var depth = parseFloat(mod.cut_depth.value)\n                var tip = parseFloat(mod.tip_dia.value)\n                \n                // Effective Diameter Calculation\n                var effective_dia = tip + (2 * depth * Math.tan(angle_rad))\n                \n                // --- Update Live Values in SVG ---\n                if (mod.svg_tip) mod.svg_tip.textContent = tip.toFixed(2) + \"mm\"\n                if (mod.svg_depth) mod.svg_depth.textContent = depth.toFixed(2) + \"mm\"\n                if (mod.svg_eff) mod.svg_eff.textContent = effective_dia.toFixed(3) + \"mm\"\n\n                var obj = {\n                    'tool': mod.tool_num.value,\n                    'dia': effective_dia.toFixed(4),\n                    'cut': depth,\n                    'max': parseFloat(mod.max_depth.value)\n                }\n                mods.output(mod, 'settings', obj)\n            }\n        }\n    }\n    var interface = function(div) {\n        mod.div = div\n        \n        // --- LIVE SVG DIAGRAM ---\n        var svgNS = \"http://www.w3.org/2000/svg\"\n        var svg = document.createElementNS(svgNS, \"svg\")\n        svg.setAttribute('width', '160')\n        svg.setAttribute('height', '110')\n        svg.style.display = 'block'\n        svg.style.margin = 'auto'\n        svg.style.backgroundColor = '#f8f8f8'\n        svg.style.border = '1px solid #ddd'\n        \n        // V-Shape Path\n        var vpath = document.createElementNS(svgNS, 'path')\n        vpath.setAttribute('d', 'M 30,15 L 75,85 L 85,85 L 130,15')\n        vpath.setAttribute('stroke', '#333')\n        vpath.setAttribute('fill', '#e0e0e0')\n        vpath.setAttribute('stroke-width', '2')\n        svg.appendChild(vpath)\n\n        // Depth/Diameter Indicator Lines\n        var dline = document.createElementNS(svgNS, 'line')\n        dline.setAttribute('x1', '50'); dline.setAttribute('y1', '45')\n        dline.setAttribute('x2', '110'); dline.setAttribute('y2', '45')\n        dline.setAttribute('stroke', 'red'); dline.setAttribute('stroke-dasharray', '3')\n        svg.appendChild(dline)\n\n        // Text Elements for Live Values\n        function createSvgText(x, y, color, id_var) {\n            var t = document.createElementNS(svgNS, 'text')\n            t.setAttribute('x', x); t.setAttribute('y', y)\n            t.setAttribute('font-size', '11'); t.setAttribute('fill', color)\n            t.setAttribute('font-weight', 'bold')\n            t.setAttribute('text-anchor', 'middle')\n            mod[id_var] = t\n            svg.appendChild(t)\n        }\n        \n        createSvgText(80, 100, 'black', 'svg_tip')   // Value at bottom\n        createSvgText(25, 50, 'red', 'svg_depth')    // Value at side\n        createSvgText(80, 40, 'blue', 'svg_eff')     // Value at dash line\n        \n        div.appendChild(svg)\n        div.appendChild(document.createElement('br'))\n\n        // --- INPUT FIELDS ---\n        function createInput(label, variable) {\n            div.appendChild(document.createTextNode(label))\n            var i = document.createElement('input'); i.type = 'text'; i.size = 6;\n            i.addEventListener('input', outputs.settings.event)\n            div.appendChild(i); mod[variable] = i;\n            div.appendChild(document.createElement('br'))\n        }\n        createInput('Tool Number: ', 'tool_num')\n        createInput('V-Angle (deg): ', 'angle')\n        createInput('Tip Dia (mm): ', 'tip_dia')\n        createInput('Cut Depth (mm): ', 'cut_depth')\n        createInput('Max Depth (mm): ', 'max_depth')\n        \n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.appendChild(document.createTextNode('Sync'))\n        btn.addEventListener('click', outputs.settings.event)\n        div.appendChild(btn)\n    }\n    return { mod: mod, name: name, init: init, inputs: inputs, outputs: outputs, interface: interface }\n}())",
      "top": "1318.8539451678291",
      "left": "1969.9654836074706",
      "filename": "modules/processes/mill%20raster/2D_dual.js",
      "inputs": {},
      "outputs": {}
    },
    "0.4309394279684452": {
      "definition": "//\n// mill triple-tool 2D\n//\n// Quentin Bolsee\n// (c) Massachusetts Institute of Technology 2024\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function() {\n    //\n    // module globals\n    //\n    var mod = {}\n    //\n    // name\n    //\n    var name = 'mill triple-tool 2D'\n    //\n    // initialization\n    //\n    var init = function() {\n        // tool 1 settings\n        mod.tool1.value = 2\n        mod.dia1_mm.value = 0.4\n        mod.cut1_mm.value = 0.09\n        mod.max1_mm.value = 0.09\n        mod.stepover1.value = 0.3\n        mod.number1.value = 2\n        mod.imageInfo1 = {};\n\n        // tool 2 settings\n        mod.tool2.value = 3\n        mod.dia2_mm.value = 0.8\n        mod.cut2_mm.value = 0.09\n        mod.max2_mm.value = 0.09\n        mod.stepover2.value = 0.5\n        mod.number2.value = 2\n        mod.imageInfo2 = {};\n\n        // tool 3 settings\n        mod.tool3.value = 3\n        mod.dia3_mm.value = 0.8\n        mod.cut3_mm.value = 0.6\n        mod.max3_mm.value = 1.75\n        mod.stepover3.value = 0.5\n        mod.number3.value = 1\n        mod.imageInfo3 = {};\n\n        // output info\n        mod.paths = [];\n        mod.tools = [];\n        mod.name = \"\";\n        mod.dpi = 1000;\n        mod.width = 0;\n        mod.height = 0;\n        mod.depth = 0;\n    }\n    //\n    // inputs\n    //\n    var inputs = {\n        // --- NEW V-BIT INPUT ---\n        v_input: {\n            type: 'object',\n            event: function(evt) {\n                mod.tool1.value = evt.detail.tool;\n                mod.dia1_mm.value = evt.detail.dia;\n                mod.cut1_mm.value = evt.detail.cut;\n                mod.max1_mm.value = evt.detail.max;\n            }\n        },\n        image1: {\n            type: 'RGBA',\n            event: function(evt) {\n                var input = evt.detail\n                var ctx = mod.image1.getContext(\"2d\")\n                ctx.canvas.width = input.width\n                ctx.canvas.height = input.height\n                ctx.putImageData(input,0,0)\n            }\n        },\n        imageInfo1: {\n            type: 'object',\n            event: function(evt) {\n                mod.imageInfo1 = evt.detail\n            }\n        },\n        image2: {\n            type: 'RGBA',\n            event: function(evt) {\n                var input = evt.detail\n                var ctx = mod.image2.getContext(\"2d\")\n                ctx.canvas.width = input.width\n                ctx.canvas.height = input.height\n                ctx.putImageData(input,0,0)\n            }\n        },\n        imageInfo2: {\n            type: 'object',\n            event: function(evt) {\n                mod.imageInfo2 = evt.detail\n            }\n        },\n        image3: {\n            type: 'RGBA',\n            event: function(evt) {\n                var input = evt.detail\n                var ctx = mod.image3.getContext(\"2d\")\n                ctx.canvas.width = input.width\n                ctx.canvas.height = input.height\n                ctx.putImageData(input,0,0)\n            }\n        },\n        imageInfo3: {\n            type: 'object',\n            event: function(evt) {\n                mod.imageInfo3 = evt.detail\n            }\n        },\n        toolpath: {\n            type: 'object',\n            event: function(evt) {\n                if (mod.label.nodeValue == 'calculating') {\n                    var toolpath = structuredClone(evt.detail);\n                    mod.toolCount += 1\n                    if (mod.toolCount == 1) {\n                        mod.toolpath_array.push(toolpath)\n                        outputs.imageInfo.event()\n                        outputs.image.event()\n                    } else if (mod.toolCount == 2) {\n                        mod.toolpath_array.push(toolpath)\n                        outputs.imageInfo.event()\n                        outputs.image.event()\n                    } else {\n                        mod.toolpath_array.push(toolpath)\n                        mod.label.nodeValue = 'calculate'\n                        mod.labelspan.style.fontWeight = 'normal'\n                        merge_toolpaths();\n                        outputs.toolpaths.event()\n                    }\n                }\n            }\n        },\n        distance: {\n            type: 'RGBA',\n            event: function(evt) {\n                setTimeout(outputs.settings.event, 1000);\n            }\n        },\n    }\n    //\n    // outputs\n    //\n    var outputs = {\n        image: {\n            type: 'RGBA',\n            event: function() {\n                var ctx;\n                var img;\n                if (mod.toolCount == 0) {\n                    ctx = mod.image1.getContext(\"2d\");\n                    img = ctx.getImageData(0,0,mod.image1.width,mod.image1.height)\n                } else if (mod.toolCount == 1) {\n                    ctx = mod.image2.getContext(\"2d\");\n                    img = ctx.getImageData(0,0,mod.image2.width,mod.image2.height)\n                } else {\n                    ctx = mod.image3.getContext(\"2d\");\n                    img = ctx.getImageData(0,0,mod.image3.width,mod.image3.height)\n                }\n                mods.output(mod,'image',img)\n            }\n        },\n        imageInfo: {\n            type: 'object',\n            event: function() {\n                cmd = {}\n                if (mod.toolCount == 0) {\n                    cmd = mod.imageInfo1;\n                } else if (mod.toolCount == 1) {\n                    cmd = mod.imageInfo2;\n                } else {\n                    cmd = mod.imageInfo3;\n                }\n                mods.output(mod, 'imageInfo', cmd)\n            }\n        },\n        settings: {\n            type: 'object',\n            event: function() {\n                var cmd;\n                if (mod.toolCount == 0) {\n                    cmd = {\n                        'tool diameter (mm)': mod.dia1_mm.value,\n                        'cut depth (mm)': mod.cut1_mm.value,\n                        'max depth (mm)': mod.max1_mm.value,\n                        'offset stepover': mod.stepover1.value,\n                        'offset number': mod.number1.value,\n                        'calculate': true\n                    }\n                } else if (mod.toolCount == 1) {\n                    cmd = {\n                        'tool diameter (mm)': mod.dia2_mm.value,\n                        'cut depth (mm)': mod.cut2_mm.value,\n                        'max depth (mm)': mod.max2_mm.value,\n                        'offset stepover': mod.stepover2.value,\n                        'offset number': mod.number2.value,\n                        'calculate': true\n                    }\n                } else {\n                    cmd = {\n                        'tool diameter (mm)': mod.dia3_mm.value,\n                        'cut depth (mm)': mod.cut3_mm.value,\n                        'max depth (mm)': mod.max3_mm.value,\n                        'offset stepover': mod.stepover3.value,\n                        'offset number': mod.number3.value,\n                        'calculate': true\n                    }\n                }\n                mods.output(mod, 'settings', cmd)\n            }\n        },\n        toolpaths: {\n            type: 'object',\n            event: function() {\n                cmd = {}\n                cmd.paths = mod.paths\n                cmd.tools = mod.tools\n                cmd.name = mod.name\n                cmd.dpi = mod.dpi\n                cmd.width = mod.width\n                cmd.height = mod.height\n                cmd.depth = mod.depth\n                mods.output(mod, 'toolpaths', cmd)\n            }\n        }\n    }\n    var merge_toolpaths = function() {\n        var t1 = mod.toolpath_array[0]\n        var t2 = mod.toolpath_array[1]\n        var t3 = mod.toolpath_array[2]\n\n        mod.name = `job`\n        mod.paths = [t1.path, t2.path, t3.path]\n        mod.tools = [mod.tool1.value, mod.tool2.value, mod.tool3.value]\n        mod.dpi = t1.dpi\n        mod.width = t1.width\n        mod.height = t1.height\n        mod.depth = Math.max(t1.depth, t2.depth, t3.depth)\n    }\n    var click_calculate = function() {\n        mod.label.nodeValue = 'calculating'\n        mod.labelspan.style.fontWeight = 'bold'\n        mod.toolCount = 0\n        mod.toolpath_array = []\n\n        outputs.imageInfo.event()\n        outputs.image.event()\n    }\n    //\n    // interface\n    //\n    var interface = function(div) {\n        mod.div = div\n\n        var canvas = document.createElement('canvas')\n        mod.image1 = canvas\n        var canvas = document.createElement('canvas')\n        mod.image2 = canvas\n        var canvas = document.createElement('canvas')\n        mod.image3 = canvas\n\n        // Tool 1\n        div.appendChild(document.createTextNode('tool 1:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.tool1 = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('tool 1 diameter (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.dia1_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('cut depth 1 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.cut1_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('max depth 1 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.max1_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset number 1:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.number1 = input;\n        div.appendChild(document.createTextNode(' (0 = fill)'))\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset stepover 1: '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.stepover1 = input;\n        div.appendChild(document.createTextNode(' (1 = diameter)'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createElement('br'))\n\n        // Tool 2\n        div.appendChild(document.createTextNode('tool 2:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.tool2 = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('tool 2 diameter (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.dia2_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('cut depth 2 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.cut2_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('max depth 2 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.max2_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset number 2:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.number2 = input;\n        div.appendChild(document.createTextNode(' (0 = fill)'))\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset stepover 2: '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.stepover2 = input;\n        div.appendChild(document.createTextNode(' (1 = diameter)'))\n        div.appendChild(document.createElement('br'))\n        div.appendChild(document.createElement('br'))\n\n        // Tool 3\n        div.appendChild(document.createTextNode('tool 3:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.tool3 = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('tool 3 diameter (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.dia3_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('cut depth 3 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.cut3_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('max depth 3 (mm): '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.max3_mm = input;\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset number 3:'))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.number3 = input;\n        div.appendChild(document.createTextNode(' (0 = fill)'))\n        div.appendChild(document.createElement('br'))\n\n        div.appendChild(document.createTextNode('offset stepover 3: '))\n        var input = document.createElement('input'); input.type = 'text'; input.size = 6;\n        div.appendChild(input); mod.stepover3 = input;\n        div.appendChild(document.createTextNode(' (1 = diameter)'))\n        div.appendChild(document.createElement('br'))\n\n        // Calculate\n        var btn = document.createElement('button')\n        btn.style.padding = mods.ui.padding\n        btn.style.margin = 1\n        var span = document.createElement('span')\n        var text = document.createTextNode('calculate')\n        mod.label = text\n        span.appendChild(text)\n        mod.labelspan = span\n        btn.appendChild(span)\n        btn.addEventListener('click', click_calculate)\n        div.appendChild(btn)\n        div.appendChild(document.createTextNode(' '))\n    }\n    //\n    // return values\n    //\n    return ({\n        name: name,\n        init: init,\n        inputs: inputs,\n        outputs: outputs,\n        interface: interface\n    })\n}())",
      "top": "775.326150577454",
      "left": "1954.106395918659",
      "filename": "modules/processes/mill%20raster/2D_triple.js",
      "inputs": {},
      "outputs": {}
    }
  },
  "links": [
    "{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.608045696421487\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.608045696421487\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6845090847457781\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.8534257529052124\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.543885257115264\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image2\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6627695112215364\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.543885257115264\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image1\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.037680464012921244\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8534257529052124\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.037680464012921244\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7353074975250568\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.037680464012921244\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49202738148831227\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.037680464012921244\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49202738148831227\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpaths\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3947110891103697\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpaths\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.3947110891103697\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6845090847457781\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.06233643688719437\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"v_input\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6627695112215364\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image1\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6627695112215364\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo1\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.543885257115264\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image2\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.6627695112215364\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo2\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.49202738148831227\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image3\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.037680464012921244\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo3\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.608045696421487\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distance\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.608045696421487\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.608045696421487\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}",
    "{\"source\":\"{\\\"id\\\":\\\"0.4309394279684452\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpaths\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpaths\\\"}\"}"
  ]
}
